@love-moon/ai-sdk 0.2.17 → 0.2.18
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/client.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export function createAiSession(backend: any, options?: {}): import("./session-factory.js").CodexAppServerSession | RemoteAiSession;
|
|
1
|
+
export function createAiSession(backend: any, options?: {}): import("./session-factory.js").CodexAppServerSession | import("./session-factory.js").ClaudeAgentSdkSession | RemoteAiSession;
|
|
2
2
|
export class RemoteAiSession extends EventEmitter<[never]> {
|
|
3
3
|
constructor(backend: any, options?: {});
|
|
4
4
|
backend: string;
|
|
@@ -7,7 +7,7 @@ export class RemoteAiSession extends EventEmitter<[never]> {
|
|
|
7
7
|
variant: string;
|
|
8
8
|
threadIdValue: any;
|
|
9
9
|
threadOptionsValue: {
|
|
10
|
-
model:
|
|
10
|
+
model: any;
|
|
11
11
|
};
|
|
12
12
|
useSessionFileReplyStreamValue: boolean;
|
|
13
13
|
sessionInfo: any;
|
|
@@ -33,7 +33,7 @@ export class RemoteAiSession extends EventEmitter<[never]> {
|
|
|
33
33
|
readyPromise: Promise<any>;
|
|
34
34
|
get threadId(): any;
|
|
35
35
|
get threadOptions(): {
|
|
36
|
-
model:
|
|
36
|
+
model: any;
|
|
37
37
|
};
|
|
38
38
|
getSnapshot(): {
|
|
39
39
|
sessionInfo: any;
|
package/dist/client.js
CHANGED
|
@@ -2,7 +2,7 @@ import { EventEmitter } from "node:events";
|
|
|
2
2
|
import { spawn } from "node:child_process";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import readline from "node:readline";
|
|
5
|
-
import { assertSupportedBackend, createLocalAiSession,
|
|
5
|
+
import { assertSupportedBackend, createLocalAiSession, providerVariantForBackend, } from "./session-factory.js";
|
|
6
6
|
import { normalizeLogger, reviveError } from "./shared.js";
|
|
7
7
|
const WORKER_PATH = fileURLToPath(new URL("./worker.js", import.meta.url));
|
|
8
8
|
function sanitizeOptionsForWorker(options = {}) {
|
|
@@ -29,13 +29,15 @@ export class RemoteAiSession extends EventEmitter {
|
|
|
29
29
|
this.backend = assertSupportedBackend(backend);
|
|
30
30
|
this.options = options;
|
|
31
31
|
this.logger = normalizeLogger(options.logger);
|
|
32
|
-
this.variant =
|
|
32
|
+
this.variant = providerVariantForBackend(this.backend);
|
|
33
33
|
this.threadIdValue =
|
|
34
34
|
typeof options.resumeSessionId === "string" && options.resumeSessionId.trim()
|
|
35
35
|
? options.resumeSessionId.trim()
|
|
36
36
|
: "";
|
|
37
37
|
this.threadOptionsValue = {
|
|
38
|
-
model:
|
|
38
|
+
model: typeof options.model === "string" && options.model.trim()
|
|
39
|
+
? options.model.trim()
|
|
40
|
+
: this.backend || "unknown",
|
|
39
41
|
};
|
|
40
42
|
this.useSessionFileReplyStreamValue = true;
|
|
41
43
|
this.sessionInfo = null;
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
export class ClaudeAgentSdkSession extends EventEmitter<[never]> {
|
|
2
|
+
constructor(backend: any, options?: {});
|
|
3
|
+
backend: string;
|
|
4
|
+
options: {};
|
|
5
|
+
logger: any;
|
|
6
|
+
cwd: any;
|
|
7
|
+
resumeSessionId: any;
|
|
8
|
+
sessionId: any;
|
|
9
|
+
sessionInfo: {
|
|
10
|
+
backend: string;
|
|
11
|
+
sessionId: any;
|
|
12
|
+
} | null;
|
|
13
|
+
history: any[];
|
|
14
|
+
pendingHistorySeed: boolean;
|
|
15
|
+
closeRequested: boolean;
|
|
16
|
+
closed: boolean;
|
|
17
|
+
closeWaiters: Set<any>;
|
|
18
|
+
sessionMessageHandler: any;
|
|
19
|
+
workingStatusHandler: any;
|
|
20
|
+
activeReplyTarget: string;
|
|
21
|
+
lastReplyTarget: string;
|
|
22
|
+
manualResumeReady: boolean;
|
|
23
|
+
currentTurn: {
|
|
24
|
+
abortController: AbortController;
|
|
25
|
+
emittedAssistantMessage: boolean;
|
|
26
|
+
fullText: string;
|
|
27
|
+
items: never[];
|
|
28
|
+
query: null;
|
|
29
|
+
resultMessage: null;
|
|
30
|
+
terminalWorkingStatusEmitted: boolean;
|
|
31
|
+
} | null;
|
|
32
|
+
lastResult: any;
|
|
33
|
+
rateLimitInfo: any;
|
|
34
|
+
turnDeadlineMs: any;
|
|
35
|
+
sdkModulePromise: Promise<any> | Promise<typeof import("@anthropic-ai/claude-agent-sdk")> | null;
|
|
36
|
+
env: any;
|
|
37
|
+
writeLog(message: any): void;
|
|
38
|
+
trace(message: any): void;
|
|
39
|
+
get threadId(): any;
|
|
40
|
+
get threadOptions(): {
|
|
41
|
+
model: any;
|
|
42
|
+
};
|
|
43
|
+
getSnapshot(): {
|
|
44
|
+
backend: string;
|
|
45
|
+
provider: string;
|
|
46
|
+
cwd: any;
|
|
47
|
+
sessionId: any;
|
|
48
|
+
sessionInfo: {
|
|
49
|
+
backend: string;
|
|
50
|
+
sessionId: any;
|
|
51
|
+
} | null;
|
|
52
|
+
useSessionFileReplyStream: boolean;
|
|
53
|
+
resumeReady: boolean;
|
|
54
|
+
manualResume: {
|
|
55
|
+
ready: boolean;
|
|
56
|
+
command: string;
|
|
57
|
+
} | null;
|
|
58
|
+
};
|
|
59
|
+
getSessionInfo(): {
|
|
60
|
+
backend: string;
|
|
61
|
+
sessionId: any;
|
|
62
|
+
} | null;
|
|
63
|
+
ensureSessionInfo(): Promise<{
|
|
64
|
+
backend: string;
|
|
65
|
+
sessionId: any;
|
|
66
|
+
} | null>;
|
|
67
|
+
getSessionUsageSummary(): Promise<{
|
|
68
|
+
sessionId: any;
|
|
69
|
+
sessionFilePath: undefined;
|
|
70
|
+
totalCostUsd: number | undefined;
|
|
71
|
+
usage: any;
|
|
72
|
+
modelUsage: any;
|
|
73
|
+
rateLimits: any;
|
|
74
|
+
manualResume: {
|
|
75
|
+
ready: boolean;
|
|
76
|
+
command: string;
|
|
77
|
+
} | null;
|
|
78
|
+
}>;
|
|
79
|
+
usesSessionFileReplyStream(): boolean;
|
|
80
|
+
setSessionMessageHandler(handler: any): void;
|
|
81
|
+
setWorkingStatusHandler(handler: any): void;
|
|
82
|
+
setSessionReplyTarget(replyTo: any): void;
|
|
83
|
+
getCurrentReplyTarget(): string | undefined;
|
|
84
|
+
emitWorkingStatus(payload: any, onProgress?: null): Promise<void>;
|
|
85
|
+
emitAssistantMessage(text: any): Promise<void>;
|
|
86
|
+
emitTerminalWorkingStatus(currentTurn: any, payload: any, onProgress?: null): Promise<void>;
|
|
87
|
+
createSessionClosedError(): Error;
|
|
88
|
+
createTurnTimeoutError(timeoutMs: any): Error;
|
|
89
|
+
createCloseGuard(onClose: any): {
|
|
90
|
+
promise: Promise<any>;
|
|
91
|
+
cleanup: () => void;
|
|
92
|
+
};
|
|
93
|
+
createTurnTimeoutGuard(onTimeout: any): {
|
|
94
|
+
promise: Promise<any>;
|
|
95
|
+
cleanup: () => void;
|
|
96
|
+
};
|
|
97
|
+
flushCloseWaiters(): void;
|
|
98
|
+
buildPrompt(promptText: any, { useInitialImages }?: {
|
|
99
|
+
useInitialImages?: boolean | undefined;
|
|
100
|
+
}): string;
|
|
101
|
+
updateSessionInfo(sessionId: any): void;
|
|
102
|
+
maybeEmitAuthRequired(message: any, extraMessage?: string): void;
|
|
103
|
+
buildSdkOptions(abortController: any): {
|
|
104
|
+
abortController: any;
|
|
105
|
+
cwd: any;
|
|
106
|
+
env: any;
|
|
107
|
+
permissionMode: "default" | "acceptEdits" | "bypassPermissions" | "plan" | "dontAsk";
|
|
108
|
+
settingSources: string[];
|
|
109
|
+
persistSession: boolean;
|
|
110
|
+
};
|
|
111
|
+
getSdkModule(): Promise<any>;
|
|
112
|
+
handleSdkMessage(message: any, currentTurn: any, { onProgress }: {
|
|
113
|
+
onProgress: any;
|
|
114
|
+
}): Promise<void>;
|
|
115
|
+
interruptCurrentTurn(): Promise<void>;
|
|
116
|
+
runTurn(promptText: any, { useInitialImages, onProgress }?: {
|
|
117
|
+
useInitialImages?: boolean | undefined;
|
|
118
|
+
onProgress?: null | undefined;
|
|
119
|
+
}): Promise<{
|
|
120
|
+
text: string;
|
|
121
|
+
usage: null;
|
|
122
|
+
items: never[];
|
|
123
|
+
events: never[];
|
|
124
|
+
provider?: undefined;
|
|
125
|
+
metadata?: undefined;
|
|
126
|
+
} | {
|
|
127
|
+
text: any;
|
|
128
|
+
usage: any;
|
|
129
|
+
items: never[];
|
|
130
|
+
events: never[];
|
|
131
|
+
provider: string;
|
|
132
|
+
metadata: {
|
|
133
|
+
source: string;
|
|
134
|
+
sessionId: any;
|
|
135
|
+
totalCostUsd: number | undefined;
|
|
136
|
+
modelUsage: any;
|
|
137
|
+
};
|
|
138
|
+
}>;
|
|
139
|
+
close(): Promise<void>;
|
|
140
|
+
}
|
|
141
|
+
import { EventEmitter } from "node:events";
|
|
@@ -0,0 +1,793 @@
|
|
|
1
|
+
import { EventEmitter } from "node:events";
|
|
2
|
+
import { emitLog, getBoundedEnvInt, loadEnvConfig, normalizeLogger, proxyToEnv, sanitizeForLog, } from "../shared.js";
|
|
3
|
+
const DEFAULT_TURN_DEADLINE_MS = 12 * 60 * 1000;
|
|
4
|
+
const MIN_TURN_DEADLINE_MS = 30 * 1000;
|
|
5
|
+
const MAX_TURN_DEADLINE_MS = 30 * 60 * 1000;
|
|
6
|
+
const DEFAULT_SETTING_SOURCES = ["user", "project", "local"];
|
|
7
|
+
const CLAUDE_PROVIDER_VARIANT = "claude-agent-sdk";
|
|
8
|
+
function waitForever() {
|
|
9
|
+
return new Promise(() => { });
|
|
10
|
+
}
|
|
11
|
+
function createTurnError(message, extras = {}) {
|
|
12
|
+
const error = new Error(message);
|
|
13
|
+
for (const [key, value] of Object.entries(extras)) {
|
|
14
|
+
error[key] = value;
|
|
15
|
+
}
|
|
16
|
+
return error;
|
|
17
|
+
}
|
|
18
|
+
function normalizeClaudeBackend(backend) {
|
|
19
|
+
const normalized = String(backend || "").trim().toLowerCase();
|
|
20
|
+
if (normalized === "claude-code") {
|
|
21
|
+
return "claude";
|
|
22
|
+
}
|
|
23
|
+
return normalized || "claude";
|
|
24
|
+
}
|
|
25
|
+
function normalizeList(value) {
|
|
26
|
+
if (!Array.isArray(value)) {
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
const normalized = value
|
|
30
|
+
.map((item) => (typeof item === "string" ? item.trim() : ""))
|
|
31
|
+
.filter(Boolean);
|
|
32
|
+
return normalized.length > 0 ? normalized : undefined;
|
|
33
|
+
}
|
|
34
|
+
function normalizeSettingSources(value) {
|
|
35
|
+
const normalized = normalizeList(value);
|
|
36
|
+
return normalized && normalized.length > 0 ? normalized : [...DEFAULT_SETTING_SOURCES];
|
|
37
|
+
}
|
|
38
|
+
function normalizePermissionMode(value) {
|
|
39
|
+
const normalized = typeof value === "string" ? value.trim() : "";
|
|
40
|
+
switch (normalized) {
|
|
41
|
+
case "default":
|
|
42
|
+
case "acceptEdits":
|
|
43
|
+
case "bypassPermissions":
|
|
44
|
+
case "plan":
|
|
45
|
+
case "dontAsk":
|
|
46
|
+
return normalized;
|
|
47
|
+
default:
|
|
48
|
+
return "bypassPermissions";
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function normalizeText(value) {
|
|
52
|
+
return typeof value === "string" ? value : "";
|
|
53
|
+
}
|
|
54
|
+
function extractAssistantText(message) {
|
|
55
|
+
const content = Array.isArray(message?.content) ? message.content : [];
|
|
56
|
+
return content
|
|
57
|
+
.map((block) => (block?.type === "text" && typeof block.text === "string" ? block.text : ""))
|
|
58
|
+
.filter(Boolean)
|
|
59
|
+
.join("");
|
|
60
|
+
}
|
|
61
|
+
function extractResultErrorMessage(resultMessage) {
|
|
62
|
+
if (!resultMessage || typeof resultMessage !== "object") {
|
|
63
|
+
return "Claude turn failed";
|
|
64
|
+
}
|
|
65
|
+
const errors = Array.isArray(resultMessage.errors) ? resultMessage.errors.filter(Boolean) : [];
|
|
66
|
+
if (errors.length > 0) {
|
|
67
|
+
return String(errors[0]);
|
|
68
|
+
}
|
|
69
|
+
const subtype = typeof resultMessage.subtype === "string" ? resultMessage.subtype.trim() : "";
|
|
70
|
+
if (subtype) {
|
|
71
|
+
return `Claude turn failed (${subtype})`;
|
|
72
|
+
}
|
|
73
|
+
return "Claude turn failed";
|
|
74
|
+
}
|
|
75
|
+
function toolPhaseForName(toolName) {
|
|
76
|
+
const normalized = String(toolName || "").trim().toLowerCase();
|
|
77
|
+
if (!normalized) {
|
|
78
|
+
return "tool_call";
|
|
79
|
+
}
|
|
80
|
+
if (normalized.includes("bash") || normalized.includes("command") || normalized.includes("shell")) {
|
|
81
|
+
return "command_execution";
|
|
82
|
+
}
|
|
83
|
+
if (normalized.includes("edit") ||
|
|
84
|
+
normalized.includes("write") ||
|
|
85
|
+
normalized.includes("multiedit") ||
|
|
86
|
+
normalized.includes("replace")) {
|
|
87
|
+
return "file_update";
|
|
88
|
+
}
|
|
89
|
+
if (normalized.includes("read") ||
|
|
90
|
+
normalized.includes("grep") ||
|
|
91
|
+
normalized.includes("glob") ||
|
|
92
|
+
normalized.includes("ls")) {
|
|
93
|
+
return "workspace_inspection";
|
|
94
|
+
}
|
|
95
|
+
if (normalized.includes("web") || normalized.includes("fetch") || normalized.includes("search")) {
|
|
96
|
+
return "web_lookup";
|
|
97
|
+
}
|
|
98
|
+
if (normalized.includes("task") || normalized.includes("agent")) {
|
|
99
|
+
return "task_progress";
|
|
100
|
+
}
|
|
101
|
+
return "tool_call";
|
|
102
|
+
}
|
|
103
|
+
function statusLineForPhase(phase, toolName = "") {
|
|
104
|
+
switch (phase) {
|
|
105
|
+
case "context_compaction":
|
|
106
|
+
return "claude compacting context";
|
|
107
|
+
case "command_execution":
|
|
108
|
+
return "claude running command";
|
|
109
|
+
case "file_update":
|
|
110
|
+
return "claude editing files";
|
|
111
|
+
case "workspace_inspection":
|
|
112
|
+
return "claude reading workspace";
|
|
113
|
+
case "web_lookup":
|
|
114
|
+
return "claude browsing";
|
|
115
|
+
case "message_aggregation":
|
|
116
|
+
return "claude composing reply";
|
|
117
|
+
case "task_progress":
|
|
118
|
+
return toolName ? `claude running ${toolName}` : "claude running task";
|
|
119
|
+
case "tool_call":
|
|
120
|
+
return toolName ? `claude calling ${toolName}` : "claude calling tool";
|
|
121
|
+
default:
|
|
122
|
+
return "claude is working";
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function sanitizeSummary(value, maxLen = 180) {
|
|
126
|
+
return sanitizeForLog(value, maxLen);
|
|
127
|
+
}
|
|
128
|
+
export class ClaudeAgentSdkSession extends EventEmitter {
|
|
129
|
+
constructor(backend, options = {}) {
|
|
130
|
+
super();
|
|
131
|
+
this.backend = normalizeClaudeBackend(backend);
|
|
132
|
+
this.options = options;
|
|
133
|
+
this.logger = normalizeLogger(options.logger);
|
|
134
|
+
this.cwd =
|
|
135
|
+
typeof options.cwd === "string" && options.cwd.trim()
|
|
136
|
+
? options.cwd.trim()
|
|
137
|
+
: process.cwd();
|
|
138
|
+
this.resumeSessionId = typeof options.resumeSessionId === "string" ? options.resumeSessionId.trim() : "";
|
|
139
|
+
this.sessionId = this.resumeSessionId || "";
|
|
140
|
+
this.sessionInfo = this.sessionId
|
|
141
|
+
? {
|
|
142
|
+
backend: this.backend,
|
|
143
|
+
sessionId: this.sessionId,
|
|
144
|
+
}
|
|
145
|
+
: null;
|
|
146
|
+
this.history = Array.isArray(options.initialHistory) ? [...options.initialHistory] : [];
|
|
147
|
+
this.pendingHistorySeed = this.history.length > 0;
|
|
148
|
+
this.closeRequested = false;
|
|
149
|
+
this.closed = false;
|
|
150
|
+
this.closeWaiters = new Set();
|
|
151
|
+
this.sessionMessageHandler = null;
|
|
152
|
+
this.workingStatusHandler = null;
|
|
153
|
+
this.activeReplyTarget = "";
|
|
154
|
+
this.lastReplyTarget = "";
|
|
155
|
+
this.manualResumeReady = Boolean(this.sessionId);
|
|
156
|
+
this.currentTurn = null;
|
|
157
|
+
this.lastResult = null;
|
|
158
|
+
this.rateLimitInfo = null;
|
|
159
|
+
this.turnDeadlineMs = getBoundedEnvInt("CONDUCTOR_TURN_DEADLINE_MS", DEFAULT_TURN_DEADLINE_MS, MIN_TURN_DEADLINE_MS, MAX_TURN_DEADLINE_MS);
|
|
160
|
+
this.sdkModulePromise = null;
|
|
161
|
+
const envConfig = loadEnvConfig(options.configFile);
|
|
162
|
+
const proxyEnv = proxyToEnv(envConfig);
|
|
163
|
+
const extraEnv = envConfig && typeof envConfig === "object" ? { ...envConfig, ...proxyEnv } : proxyEnv;
|
|
164
|
+
this.env = {
|
|
165
|
+
...extraEnv,
|
|
166
|
+
...(options.env && typeof options.env === "object" ? options.env : {}),
|
|
167
|
+
};
|
|
168
|
+
if (!this.env.CLAUDE_AGENT_SDK_CLIENT_APP) {
|
|
169
|
+
this.env.CLAUDE_AGENT_SDK_CLIENT_APP = "conductor-ai-sdk/0.0.0";
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
writeLog(message) {
|
|
173
|
+
emitLog(this.logger, message);
|
|
174
|
+
}
|
|
175
|
+
trace(message) {
|
|
176
|
+
this.writeLog(`[${this.backend}] [agent-sdk] ${message}`);
|
|
177
|
+
}
|
|
178
|
+
get threadId() {
|
|
179
|
+
return this.sessionId;
|
|
180
|
+
}
|
|
181
|
+
get threadOptions() {
|
|
182
|
+
const model = typeof this.options.model === "string" && this.options.model.trim()
|
|
183
|
+
? this.options.model.trim()
|
|
184
|
+
: this.backend;
|
|
185
|
+
return { model };
|
|
186
|
+
}
|
|
187
|
+
getSnapshot() {
|
|
188
|
+
return {
|
|
189
|
+
backend: this.backend,
|
|
190
|
+
provider: CLAUDE_PROVIDER_VARIANT,
|
|
191
|
+
cwd: this.cwd,
|
|
192
|
+
sessionId: this.sessionId || undefined,
|
|
193
|
+
sessionInfo: this.getSessionInfo(),
|
|
194
|
+
useSessionFileReplyStream: this.usesSessionFileReplyStream(),
|
|
195
|
+
resumeReady: this.manualResumeReady,
|
|
196
|
+
manualResume: this.sessionId
|
|
197
|
+
? {
|
|
198
|
+
ready: this.manualResumeReady,
|
|
199
|
+
command: `claude --resume ${this.sessionId}`,
|
|
200
|
+
}
|
|
201
|
+
: null,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
getSessionInfo() {
|
|
205
|
+
return this.sessionInfo ? { ...this.sessionInfo } : null;
|
|
206
|
+
}
|
|
207
|
+
async ensureSessionInfo() {
|
|
208
|
+
return this.getSessionInfo();
|
|
209
|
+
}
|
|
210
|
+
async getSessionUsageSummary() {
|
|
211
|
+
return {
|
|
212
|
+
sessionId: this.sessionId || undefined,
|
|
213
|
+
sessionFilePath: undefined,
|
|
214
|
+
totalCostUsd: Number.isFinite(Number(this.lastResult?.total_cost_usd))
|
|
215
|
+
? Number(this.lastResult.total_cost_usd)
|
|
216
|
+
: undefined,
|
|
217
|
+
usage: this.lastResult?.usage ? { ...this.lastResult.usage } : null,
|
|
218
|
+
modelUsage: this.lastResult?.modelUsage ? { ...this.lastResult.modelUsage } : null,
|
|
219
|
+
rateLimits: this.rateLimitInfo ? { ...this.rateLimitInfo } : null,
|
|
220
|
+
manualResume: this.sessionId
|
|
221
|
+
? {
|
|
222
|
+
ready: this.manualResumeReady,
|
|
223
|
+
command: `claude --resume ${this.sessionId}`,
|
|
224
|
+
}
|
|
225
|
+
: null,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
usesSessionFileReplyStream() {
|
|
229
|
+
return true;
|
|
230
|
+
}
|
|
231
|
+
setSessionMessageHandler(handler) {
|
|
232
|
+
this.sessionMessageHandler = typeof handler === "function" ? handler : null;
|
|
233
|
+
}
|
|
234
|
+
setWorkingStatusHandler(handler) {
|
|
235
|
+
this.workingStatusHandler = typeof handler === "function" ? handler : null;
|
|
236
|
+
}
|
|
237
|
+
setSessionReplyTarget(replyTo) {
|
|
238
|
+
const normalizedReplyTo = typeof replyTo === "string" ? replyTo.trim() : "";
|
|
239
|
+
this.activeReplyTarget = normalizedReplyTo;
|
|
240
|
+
if (normalizedReplyTo) {
|
|
241
|
+
this.lastReplyTarget = normalizedReplyTo;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
getCurrentReplyTarget() {
|
|
245
|
+
return this.activeReplyTarget || this.lastReplyTarget || undefined;
|
|
246
|
+
}
|
|
247
|
+
async emitWorkingStatus(payload, onProgress = null) {
|
|
248
|
+
const normalized = {
|
|
249
|
+
source: CLAUDE_PROVIDER_VARIANT,
|
|
250
|
+
reply_in_progress: Boolean(payload?.reply_in_progress),
|
|
251
|
+
replyTo: payload?.replyTo || this.getCurrentReplyTarget(),
|
|
252
|
+
state: payload?.state,
|
|
253
|
+
phase: payload?.phase,
|
|
254
|
+
status_line: payload?.status_line,
|
|
255
|
+
status_done_line: payload?.status_done_line,
|
|
256
|
+
reply_preview: payload?.reply_preview,
|
|
257
|
+
thread_id: this.sessionId || undefined,
|
|
258
|
+
};
|
|
259
|
+
if (typeof onProgress === "function") {
|
|
260
|
+
onProgress(normalized);
|
|
261
|
+
}
|
|
262
|
+
if (typeof this.workingStatusHandler === "function") {
|
|
263
|
+
await this.workingStatusHandler(normalized);
|
|
264
|
+
}
|
|
265
|
+
this.emit("working_status", normalized);
|
|
266
|
+
}
|
|
267
|
+
async emitAssistantMessage(text) {
|
|
268
|
+
const payload = {
|
|
269
|
+
text,
|
|
270
|
+
preserveWhitespace: true,
|
|
271
|
+
source: CLAUDE_PROVIDER_VARIANT,
|
|
272
|
+
replyTo: this.getCurrentReplyTarget(),
|
|
273
|
+
sessionId: this.sessionId || undefined,
|
|
274
|
+
sessionFilePath: undefined,
|
|
275
|
+
timestamp: new Date().toISOString(),
|
|
276
|
+
};
|
|
277
|
+
if (typeof this.sessionMessageHandler === "function") {
|
|
278
|
+
await this.sessionMessageHandler(payload);
|
|
279
|
+
}
|
|
280
|
+
this.emit("assistant_message", payload);
|
|
281
|
+
}
|
|
282
|
+
async emitTerminalWorkingStatus(currentTurn, payload, onProgress = null) {
|
|
283
|
+
if (!currentTurn || currentTurn.terminalWorkingStatusEmitted) {
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
currentTurn.terminalWorkingStatusEmitted = true;
|
|
287
|
+
await this.emitWorkingStatus({
|
|
288
|
+
...payload,
|
|
289
|
+
reply_in_progress: false,
|
|
290
|
+
}, onProgress);
|
|
291
|
+
}
|
|
292
|
+
createSessionClosedError() {
|
|
293
|
+
const error = new Error("Claude Agent SDK session closed");
|
|
294
|
+
error.reason = "session_closed";
|
|
295
|
+
return error;
|
|
296
|
+
}
|
|
297
|
+
createTurnTimeoutError(timeoutMs) {
|
|
298
|
+
const seconds = Math.max(1, Math.round(timeoutMs / 1000));
|
|
299
|
+
const error = new Error(`Turn exceeded hard deadline (${seconds}s)`);
|
|
300
|
+
error.reason = "turn_timeout";
|
|
301
|
+
error.timeoutMs = timeoutMs;
|
|
302
|
+
return error;
|
|
303
|
+
}
|
|
304
|
+
createCloseGuard(onClose) {
|
|
305
|
+
if (this.closeRequested) {
|
|
306
|
+
return {
|
|
307
|
+
promise: Promise.reject(this.createSessionClosedError()),
|
|
308
|
+
cleanup: () => { },
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
let waiter = null;
|
|
312
|
+
const promise = new Promise((_, reject) => {
|
|
313
|
+
waiter = () => {
|
|
314
|
+
try {
|
|
315
|
+
onClose?.();
|
|
316
|
+
}
|
|
317
|
+
catch {
|
|
318
|
+
// best effort
|
|
319
|
+
}
|
|
320
|
+
reject(this.createSessionClosedError());
|
|
321
|
+
};
|
|
322
|
+
this.closeWaiters.add(waiter);
|
|
323
|
+
});
|
|
324
|
+
return {
|
|
325
|
+
promise,
|
|
326
|
+
cleanup: () => {
|
|
327
|
+
if (waiter) {
|
|
328
|
+
this.closeWaiters.delete(waiter);
|
|
329
|
+
}
|
|
330
|
+
},
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
createTurnTimeoutGuard(onTimeout) {
|
|
334
|
+
if (!Number.isFinite(this.turnDeadlineMs) || this.turnDeadlineMs <= 0) {
|
|
335
|
+
return {
|
|
336
|
+
promise: waitForever(),
|
|
337
|
+
cleanup: () => { },
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
let timer = null;
|
|
341
|
+
const promise = new Promise((_, reject) => {
|
|
342
|
+
timer = setTimeout(() => {
|
|
343
|
+
try {
|
|
344
|
+
onTimeout?.();
|
|
345
|
+
}
|
|
346
|
+
catch {
|
|
347
|
+
// best effort
|
|
348
|
+
}
|
|
349
|
+
reject(this.createTurnTimeoutError(this.turnDeadlineMs));
|
|
350
|
+
}, this.turnDeadlineMs);
|
|
351
|
+
if (typeof timer.unref === "function") {
|
|
352
|
+
timer.unref();
|
|
353
|
+
}
|
|
354
|
+
});
|
|
355
|
+
return {
|
|
356
|
+
promise,
|
|
357
|
+
cleanup: () => {
|
|
358
|
+
if (timer) {
|
|
359
|
+
clearTimeout(timer);
|
|
360
|
+
}
|
|
361
|
+
},
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
flushCloseWaiters() {
|
|
365
|
+
if (this.closeWaiters.size === 0) {
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
for (const waiter of this.closeWaiters) {
|
|
369
|
+
try {
|
|
370
|
+
waiter();
|
|
371
|
+
}
|
|
372
|
+
catch {
|
|
373
|
+
// best effort
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
this.closeWaiters.clear();
|
|
377
|
+
}
|
|
378
|
+
buildPrompt(promptText, { useInitialImages = false } = {}) {
|
|
379
|
+
let effectivePrompt = String(promptText || "").trim();
|
|
380
|
+
if (!effectivePrompt) {
|
|
381
|
+
return "";
|
|
382
|
+
}
|
|
383
|
+
if (this.pendingHistorySeed) {
|
|
384
|
+
const historyText = this.history
|
|
385
|
+
.map((item) => {
|
|
386
|
+
const role = String(item?.role || "").toLowerCase() === "assistant" ? "Assistant" : "User";
|
|
387
|
+
return `${role}: ${String(item?.content || "").trim()}`;
|
|
388
|
+
})
|
|
389
|
+
.filter(Boolean)
|
|
390
|
+
.join("\n\n");
|
|
391
|
+
if (historyText) {
|
|
392
|
+
effectivePrompt = [
|
|
393
|
+
"Continue the existing conversation with this history.",
|
|
394
|
+
"",
|
|
395
|
+
historyText,
|
|
396
|
+
"",
|
|
397
|
+
`User: ${effectivePrompt}`,
|
|
398
|
+
].join("\n");
|
|
399
|
+
}
|
|
400
|
+
this.pendingHistorySeed = false;
|
|
401
|
+
}
|
|
402
|
+
const images = Array.isArray(this.options.initialImages) ? this.options.initialImages : [];
|
|
403
|
+
if (useInitialImages && images.length > 0) {
|
|
404
|
+
const imageContext = images.map((item, idx) => `${idx + 1}. ${item}`).join("\n");
|
|
405
|
+
effectivePrompt = `${effectivePrompt}\n\nAttached image files:\n${imageContext}`;
|
|
406
|
+
}
|
|
407
|
+
return effectivePrompt;
|
|
408
|
+
}
|
|
409
|
+
updateSessionInfo(sessionId) {
|
|
410
|
+
const normalizedSessionId = typeof sessionId === "string" ? sessionId.trim() : "";
|
|
411
|
+
if (!normalizedSessionId) {
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
const changed = this.sessionId !== normalizedSessionId;
|
|
415
|
+
this.sessionId = normalizedSessionId;
|
|
416
|
+
this.manualResumeReady = true;
|
|
417
|
+
this.sessionInfo = {
|
|
418
|
+
...(this.sessionInfo || {}),
|
|
419
|
+
backend: this.backend,
|
|
420
|
+
sessionId: normalizedSessionId,
|
|
421
|
+
};
|
|
422
|
+
if (changed) {
|
|
423
|
+
this.trace(`session ready id=${normalizedSessionId}`);
|
|
424
|
+
this.emit("session", this.getSessionInfo());
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
maybeEmitAuthRequired(message, extraMessage = "") {
|
|
428
|
+
const normalizedMessage = `${normalizeText(message)} ${normalizeText(extraMessage)}`.trim().toLowerCase();
|
|
429
|
+
if (!normalizedMessage || (!normalizedMessage.includes("auth") && !normalizedMessage.includes("login"))) {
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
this.emit("auth_required", {
|
|
433
|
+
reason: "login_required",
|
|
434
|
+
message: extraMessage || message,
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
buildSdkOptions(abortController) {
|
|
438
|
+
const permissionMode = normalizePermissionMode(this.options.permissionMode);
|
|
439
|
+
const options = {
|
|
440
|
+
abortController,
|
|
441
|
+
cwd: this.cwd,
|
|
442
|
+
env: {
|
|
443
|
+
...process.env,
|
|
444
|
+
...this.env,
|
|
445
|
+
},
|
|
446
|
+
permissionMode,
|
|
447
|
+
settingSources: normalizeSettingSources(this.options.settingSources),
|
|
448
|
+
persistSession: this.options.persistSession !== false,
|
|
449
|
+
};
|
|
450
|
+
if (permissionMode === "bypassPermissions" || this.options.allowDangerouslySkipPermissions === true) {
|
|
451
|
+
options.allowDangerouslySkipPermissions = true;
|
|
452
|
+
}
|
|
453
|
+
if (this.sessionId) {
|
|
454
|
+
options.resume = this.sessionId;
|
|
455
|
+
}
|
|
456
|
+
const passthroughKeys = [
|
|
457
|
+
"additionalDirectories",
|
|
458
|
+
"agent",
|
|
459
|
+
"agents",
|
|
460
|
+
"allowedTools",
|
|
461
|
+
"betas",
|
|
462
|
+
"debug",
|
|
463
|
+
"debugFile",
|
|
464
|
+
"disallowedTools",
|
|
465
|
+
"effort",
|
|
466
|
+
"executable",
|
|
467
|
+
"executableArgs",
|
|
468
|
+
"extraArgs",
|
|
469
|
+
"fallbackModel",
|
|
470
|
+
"forkSession",
|
|
471
|
+
"maxBudgetUsd",
|
|
472
|
+
"maxThinkingTokens",
|
|
473
|
+
"maxTurns",
|
|
474
|
+
"mcpServers",
|
|
475
|
+
"model",
|
|
476
|
+
"pathToClaudeCodeExecutable",
|
|
477
|
+
"permissionPromptToolName",
|
|
478
|
+
"plugins",
|
|
479
|
+
"resumeSessionAt",
|
|
480
|
+
"sandbox",
|
|
481
|
+
"settings",
|
|
482
|
+
"strictMcpConfig",
|
|
483
|
+
"systemPrompt",
|
|
484
|
+
"thinking",
|
|
485
|
+
"tools",
|
|
486
|
+
];
|
|
487
|
+
for (const key of passthroughKeys) {
|
|
488
|
+
const value = this.options[key];
|
|
489
|
+
if (value !== undefined) {
|
|
490
|
+
options[key] = value;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
const allowedTools = normalizeList(this.options.allowedTools);
|
|
494
|
+
if (allowedTools) {
|
|
495
|
+
options.allowedTools = allowedTools;
|
|
496
|
+
}
|
|
497
|
+
const disallowedTools = normalizeList(this.options.disallowedTools);
|
|
498
|
+
if (disallowedTools) {
|
|
499
|
+
options.disallowedTools = disallowedTools;
|
|
500
|
+
}
|
|
501
|
+
if (typeof this.options.sessionId === "string" &&
|
|
502
|
+
this.options.sessionId.trim() &&
|
|
503
|
+
(!this.sessionId || this.options.forkSession === true)) {
|
|
504
|
+
options.sessionId = this.options.sessionId.trim();
|
|
505
|
+
}
|
|
506
|
+
return options;
|
|
507
|
+
}
|
|
508
|
+
async getSdkModule() {
|
|
509
|
+
if (this.sdkModulePromise) {
|
|
510
|
+
return this.sdkModulePromise;
|
|
511
|
+
}
|
|
512
|
+
if (this.options.sdkModule && typeof this.options.sdkModule === "object") {
|
|
513
|
+
this.sdkModulePromise = Promise.resolve(this.options.sdkModule);
|
|
514
|
+
return this.sdkModulePromise;
|
|
515
|
+
}
|
|
516
|
+
this.sdkModulePromise = import("@anthropic-ai/claude-agent-sdk");
|
|
517
|
+
return this.sdkModulePromise;
|
|
518
|
+
}
|
|
519
|
+
async handleSdkMessage(message, currentTurn, { onProgress }) {
|
|
520
|
+
currentTurn.items.push(message);
|
|
521
|
+
switch (message?.type) {
|
|
522
|
+
case "system": {
|
|
523
|
+
if (message.subtype === "init") {
|
|
524
|
+
this.updateSessionInfo(message.session_id || message.sessionId);
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
if (message.subtype === "status" && message.status === "compacting") {
|
|
528
|
+
await this.emitWorkingStatus({
|
|
529
|
+
phase: "context_compaction",
|
|
530
|
+
reply_in_progress: true,
|
|
531
|
+
status_line: statusLineForPhase("context_compaction"),
|
|
532
|
+
}, onProgress);
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
if (message.subtype === "task_started") {
|
|
536
|
+
await this.emitWorkingStatus({
|
|
537
|
+
phase: "task_progress",
|
|
538
|
+
reply_in_progress: true,
|
|
539
|
+
status_line: sanitizeSummary(message.description) || statusLineForPhase("task_progress"),
|
|
540
|
+
}, onProgress);
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
if (message.subtype === "task_progress") {
|
|
544
|
+
const statusLine = sanitizeSummary(message.summary || message.description) || statusLineForPhase("task_progress");
|
|
545
|
+
await this.emitWorkingStatus({
|
|
546
|
+
phase: "task_progress",
|
|
547
|
+
reply_in_progress: true,
|
|
548
|
+
status_line: statusLine,
|
|
549
|
+
}, onProgress);
|
|
550
|
+
}
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
case "tool_progress": {
|
|
554
|
+
const phase = toolPhaseForName(message.tool_name);
|
|
555
|
+
await this.emitWorkingStatus({
|
|
556
|
+
phase,
|
|
557
|
+
reply_in_progress: true,
|
|
558
|
+
status_line: statusLineForPhase(phase, message.tool_name),
|
|
559
|
+
}, onProgress);
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
case "tool_use_summary": {
|
|
563
|
+
await this.emitWorkingStatus({
|
|
564
|
+
phase: "tool_call",
|
|
565
|
+
reply_in_progress: true,
|
|
566
|
+
status_line: sanitizeSummary(message.summary) || statusLineForPhase("tool_call"),
|
|
567
|
+
}, onProgress);
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
case "auth_status": {
|
|
571
|
+
const output = Array.isArray(message.output) ? message.output.join("\n") : "";
|
|
572
|
+
this.maybeEmitAuthRequired(output, message.error || output);
|
|
573
|
+
await this.emitWorkingStatus({
|
|
574
|
+
phase: "auth",
|
|
575
|
+
reply_in_progress: true,
|
|
576
|
+
status_line: message.isAuthenticating ? "claude authenticating" : statusLineForPhase("tool_call"),
|
|
577
|
+
}, onProgress);
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
case "assistant": {
|
|
581
|
+
this.updateSessionInfo(message.session_id || message.sessionId);
|
|
582
|
+
const text = extractAssistantText(message.message);
|
|
583
|
+
if (!text) {
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
currentTurn.emittedAssistantMessage = true;
|
|
587
|
+
currentTurn.fullText = text;
|
|
588
|
+
await this.emitWorkingStatus({
|
|
589
|
+
phase: "message_aggregation",
|
|
590
|
+
reply_in_progress: true,
|
|
591
|
+
status_line: statusLineForPhase("message_aggregation"),
|
|
592
|
+
reply_preview: sanitizeSummary(text, 120),
|
|
593
|
+
}, onProgress);
|
|
594
|
+
await this.emitAssistantMessage(text);
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
case "rate_limit_event":
|
|
598
|
+
this.updateSessionInfo(message.session_id || message.sessionId);
|
|
599
|
+
this.rateLimitInfo =
|
|
600
|
+
message.rate_limit_info && typeof message.rate_limit_info === "object"
|
|
601
|
+
? { ...message.rate_limit_info }
|
|
602
|
+
: null;
|
|
603
|
+
return;
|
|
604
|
+
case "result":
|
|
605
|
+
this.updateSessionInfo(message.session_id || message.sessionId);
|
|
606
|
+
this.lastResult = message;
|
|
607
|
+
currentTurn.resultMessage = message;
|
|
608
|
+
return;
|
|
609
|
+
default:
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
async interruptCurrentTurn() {
|
|
614
|
+
const currentTurn = this.currentTurn;
|
|
615
|
+
if (!currentTurn) {
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
try {
|
|
619
|
+
await currentTurn.query?.interrupt?.();
|
|
620
|
+
}
|
|
621
|
+
catch {
|
|
622
|
+
// best effort
|
|
623
|
+
}
|
|
624
|
+
try {
|
|
625
|
+
currentTurn.abortController?.abort?.();
|
|
626
|
+
}
|
|
627
|
+
catch {
|
|
628
|
+
// best effort
|
|
629
|
+
}
|
|
630
|
+
try {
|
|
631
|
+
currentTurn.query?.close?.();
|
|
632
|
+
}
|
|
633
|
+
catch {
|
|
634
|
+
// best effort
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
async runTurn(promptText, { useInitialImages = false, onProgress = null } = {}) {
|
|
638
|
+
if (this.closeRequested) {
|
|
639
|
+
throw this.createSessionClosedError();
|
|
640
|
+
}
|
|
641
|
+
const effectivePrompt = this.buildPrompt(promptText, { useInitialImages });
|
|
642
|
+
if (!effectivePrompt) {
|
|
643
|
+
return {
|
|
644
|
+
text: "",
|
|
645
|
+
usage: null,
|
|
646
|
+
items: [],
|
|
647
|
+
events: [],
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
if (this.currentTurn) {
|
|
651
|
+
throw createTurnError("Claude Agent SDK turn already running", {
|
|
652
|
+
reason: "turn_already_running",
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
const sdkModule = await this.getSdkModule();
|
|
656
|
+
if (!sdkModule || typeof sdkModule.query !== "function") {
|
|
657
|
+
throw new Error("Claude Agent SDK is unavailable");
|
|
658
|
+
}
|
|
659
|
+
this.history.push({ role: "user", content: promptText });
|
|
660
|
+
const abortController = new AbortController();
|
|
661
|
+
const currentTurn = {
|
|
662
|
+
abortController,
|
|
663
|
+
emittedAssistantMessage: false,
|
|
664
|
+
fullText: "",
|
|
665
|
+
items: [],
|
|
666
|
+
query: null,
|
|
667
|
+
resultMessage: null,
|
|
668
|
+
terminalWorkingStatusEmitted: false,
|
|
669
|
+
};
|
|
670
|
+
this.currentTurn = currentTurn;
|
|
671
|
+
const closeGuard = this.createCloseGuard(() => {
|
|
672
|
+
abortController.abort();
|
|
673
|
+
currentTurn.query?.close?.();
|
|
674
|
+
});
|
|
675
|
+
const turnTimeoutGuard = this.createTurnTimeoutGuard(() => {
|
|
676
|
+
abortController.abort();
|
|
677
|
+
currentTurn.query?.close?.();
|
|
678
|
+
});
|
|
679
|
+
try {
|
|
680
|
+
await this.emitWorkingStatus({
|
|
681
|
+
phase: "turn_started",
|
|
682
|
+
reply_in_progress: true,
|
|
683
|
+
status_line: "claude is working",
|
|
684
|
+
}, onProgress);
|
|
685
|
+
const query = sdkModule.query({
|
|
686
|
+
prompt: effectivePrompt,
|
|
687
|
+
options: this.buildSdkOptions(abortController),
|
|
688
|
+
});
|
|
689
|
+
currentTurn.query = query;
|
|
690
|
+
const resultMessage = await Promise.race([
|
|
691
|
+
(async () => {
|
|
692
|
+
for await (const message of query) {
|
|
693
|
+
await this.handleSdkMessage(message, currentTurn, { onProgress });
|
|
694
|
+
}
|
|
695
|
+
return currentTurn.resultMessage;
|
|
696
|
+
})(),
|
|
697
|
+
closeGuard.promise,
|
|
698
|
+
turnTimeoutGuard.promise,
|
|
699
|
+
]);
|
|
700
|
+
if (!resultMessage) {
|
|
701
|
+
throw createTurnError("Claude query ended without a result message", {
|
|
702
|
+
reason: "missing_result",
|
|
703
|
+
});
|
|
704
|
+
}
|
|
705
|
+
if (resultMessage.subtype !== "success") {
|
|
706
|
+
const errorMessage = extractResultErrorMessage(resultMessage);
|
|
707
|
+
this.maybeEmitAuthRequired(errorMessage, errorMessage);
|
|
708
|
+
await this.emitTerminalWorkingStatus(currentTurn, {
|
|
709
|
+
phase: "turn_failed",
|
|
710
|
+
status_done_line: errorMessage,
|
|
711
|
+
}, onProgress);
|
|
712
|
+
throw createTurnError(errorMessage, {
|
|
713
|
+
reason: "turn_failed",
|
|
714
|
+
turnStatus: resultMessage.subtype,
|
|
715
|
+
errors: Array.isArray(resultMessage.errors) ? [...resultMessage.errors] : [],
|
|
716
|
+
permissionDenials: Array.isArray(resultMessage.permission_denials)
|
|
717
|
+
? [...resultMessage.permission_denials]
|
|
718
|
+
: [],
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
const responseText = normalizeText(resultMessage.result) ||
|
|
722
|
+
currentTurn.fullText ||
|
|
723
|
+
extractAssistantText(currentTurn.items.find((item) => item?.type === "assistant")?.message);
|
|
724
|
+
if (!currentTurn.emittedAssistantMessage && responseText) {
|
|
725
|
+
await this.emitAssistantMessage(responseText);
|
|
726
|
+
}
|
|
727
|
+
if (responseText) {
|
|
728
|
+
this.history.push({ role: "assistant", content: responseText });
|
|
729
|
+
}
|
|
730
|
+
this.manualResumeReady = Boolean(this.sessionId);
|
|
731
|
+
this.activeReplyTarget = "";
|
|
732
|
+
await this.emitTerminalWorkingStatus(currentTurn, {
|
|
733
|
+
phase: "turn_completed",
|
|
734
|
+
status_done_line: "claude finished",
|
|
735
|
+
}, onProgress);
|
|
736
|
+
return {
|
|
737
|
+
text: responseText,
|
|
738
|
+
usage: resultMessage.usage ? { ...resultMessage.usage } : null,
|
|
739
|
+
items: currentTurn.items,
|
|
740
|
+
events: [],
|
|
741
|
+
provider: this.backend,
|
|
742
|
+
metadata: {
|
|
743
|
+
source: CLAUDE_PROVIDER_VARIANT,
|
|
744
|
+
sessionId: this.sessionId || undefined,
|
|
745
|
+
totalCostUsd: Number.isFinite(Number(resultMessage.total_cost_usd))
|
|
746
|
+
? Number(resultMessage.total_cost_usd)
|
|
747
|
+
: undefined,
|
|
748
|
+
modelUsage: resultMessage.modelUsage ? { ...resultMessage.modelUsage } : undefined,
|
|
749
|
+
},
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
catch (error) {
|
|
753
|
+
if (error?.reason === "turn_timeout") {
|
|
754
|
+
await this.interruptCurrentTurn();
|
|
755
|
+
}
|
|
756
|
+
if (!this.closeRequested && error?.reason !== "session_closed") {
|
|
757
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
758
|
+
await this.emitTerminalWorkingStatus(currentTurn, {
|
|
759
|
+
phase: "turn_failed",
|
|
760
|
+
status_done_line: errorMessage || "Claude turn failed",
|
|
761
|
+
}, onProgress);
|
|
762
|
+
}
|
|
763
|
+
if (this.closeRequested && error?.reason !== "session_closed") {
|
|
764
|
+
throw this.createSessionClosedError();
|
|
765
|
+
}
|
|
766
|
+
this.maybeEmitAuthRequired(error?.message || "", error?.message || "");
|
|
767
|
+
throw error;
|
|
768
|
+
}
|
|
769
|
+
finally {
|
|
770
|
+
this.activeReplyTarget = "";
|
|
771
|
+
if (this.currentTurn === currentTurn) {
|
|
772
|
+
this.currentTurn = null;
|
|
773
|
+
}
|
|
774
|
+
closeGuard.cleanup();
|
|
775
|
+
turnTimeoutGuard.cleanup();
|
|
776
|
+
try {
|
|
777
|
+
currentTurn.query?.close?.();
|
|
778
|
+
}
|
|
779
|
+
catch {
|
|
780
|
+
// best effort
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
async close() {
|
|
785
|
+
if (this.closed) {
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
788
|
+
this.closeRequested = true;
|
|
789
|
+
this.flushCloseWaiters();
|
|
790
|
+
await this.interruptCurrentTurn();
|
|
791
|
+
this.closed = true;
|
|
792
|
+
}
|
|
793
|
+
}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
export function normalizeBackend(backend: any): string;
|
|
2
2
|
export function isSupportedBackend(backend: any): boolean;
|
|
3
|
-
export function
|
|
4
|
-
export function
|
|
3
|
+
export function providerVariantForBackend(backend: any): "codex-app-server" | "claude-agent-sdk";
|
|
4
|
+
export function assertSupportedBackend(backend: any): "codex" | "claude";
|
|
5
|
+
export function createLocalAiSession(backend: any, options?: {}): CodexAppServerSession | ClaudeAgentSdkSession;
|
|
5
6
|
export const DEFAULT_PROVIDER_VARIANT: "codex-app-server";
|
|
6
|
-
export
|
|
7
|
+
export const CLAUDE_PROVIDER_VARIANT: "claude-agent-sdk";
|
|
7
8
|
import { CodexAppServerSession } from "./providers/codex-app-server-session.js";
|
|
9
|
+
import { ClaudeAgentSdkSession } from "./providers/claude-agent-sdk-session.js";
|
|
10
|
+
export { CodexAppServerSession, ClaudeAgentSdkSession };
|
package/dist/session-factory.js
CHANGED
|
@@ -1,23 +1,37 @@
|
|
|
1
1
|
import { CodexAppServerSession } from "./providers/codex-app-server-session.js";
|
|
2
|
+
import { ClaudeAgentSdkSession } from "./providers/claude-agent-sdk-session.js";
|
|
2
3
|
export const DEFAULT_PROVIDER_VARIANT = "codex-app-server";
|
|
4
|
+
export const CLAUDE_PROVIDER_VARIANT = "claude-agent-sdk";
|
|
3
5
|
export function normalizeBackend(backend) {
|
|
4
6
|
const normalized = String(backend || "").trim().toLowerCase();
|
|
5
7
|
if (normalized === "code") {
|
|
6
8
|
return "codex";
|
|
7
9
|
}
|
|
10
|
+
if (normalized === "claude-code") {
|
|
11
|
+
return "claude";
|
|
12
|
+
}
|
|
8
13
|
return normalized;
|
|
9
14
|
}
|
|
10
15
|
export function isSupportedBackend(backend) {
|
|
11
|
-
|
|
16
|
+
const normalized = normalizeBackend(backend);
|
|
17
|
+
return normalized === "codex" || normalized === "claude";
|
|
18
|
+
}
|
|
19
|
+
export function providerVariantForBackend(backend) {
|
|
20
|
+
return normalizeBackend(backend) === "claude" ? CLAUDE_PROVIDER_VARIANT : DEFAULT_PROVIDER_VARIANT;
|
|
12
21
|
}
|
|
13
22
|
export function assertSupportedBackend(backend) {
|
|
14
23
|
const normalized = normalizeBackend(backend);
|
|
15
|
-
if (normalized === "codex") {
|
|
24
|
+
if (normalized === "codex" || normalized === "claude") {
|
|
16
25
|
return normalized;
|
|
17
26
|
}
|
|
18
|
-
throw new Error(`Unsupported AI SDK backend "${backend}". Only codex app-server
|
|
27
|
+
throw new Error(`Unsupported AI SDK backend "${backend}". Only codex app-server and claude agent-sdk are supported.`);
|
|
19
28
|
}
|
|
20
29
|
export function createLocalAiSession(backend, options = {}) {
|
|
21
|
-
|
|
30
|
+
const normalized = assertSupportedBackend(backend);
|
|
31
|
+
if (normalized === "claude") {
|
|
32
|
+
return new ClaudeAgentSdkSession(normalized, options);
|
|
33
|
+
}
|
|
34
|
+
return new CodexAppServerSession(normalized, options);
|
|
22
35
|
}
|
|
23
36
|
export { CodexAppServerSession };
|
|
37
|
+
export { ClaudeAgentSdkSession };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@love-moon/ai-sdk",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.18",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -16,11 +16,13 @@
|
|
|
16
16
|
"prepublishOnly": "npm run build"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"
|
|
19
|
+
"@anthropic-ai/claude-agent-sdk": "^0.2.72",
|
|
20
|
+
"js-yaml": "^4.1.1",
|
|
21
|
+
"zod": "^4.1.5"
|
|
20
22
|
},
|
|
21
23
|
"devDependencies": {
|
|
22
24
|
"@types/node": "^22.10.2",
|
|
23
25
|
"typescript": "^5.6.3"
|
|
24
26
|
},
|
|
25
|
-
"gitCommitId": "
|
|
27
|
+
"gitCommitId": "942418b"
|
|
26
28
|
}
|