@love-moon/ai-sdk 0.2.38 → 0.2.40

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.
@@ -0,0 +1,633 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { spawn } from "node:child_process";
4
+ import { randomUUID } from "node:crypto";
5
+ import { EventEmitter } from "node:events";
6
+ import readline from "node:readline";
7
+ import { emitLog, getBoundedEnvInt, loadEnvConfig, normalizeLogger, parseCommandParts, proxyToEnv, sanitizeForLog, } from "../shared.js";
8
+ const DEFAULT_TURN_DEADLINE_MS = 12 * 60 * 1000;
9
+ const MIN_TURN_DEADLINE_MS = 30 * 1000;
10
+ const MAX_TURN_DEADLINE_MS = 30 * 60 * 1000;
11
+ const KIMI_PRINT_PROVIDER_VARIANT = "kimi-cli-print";
12
+ const DEFAULT_KIMI_COMMAND = "kimi";
13
+ function createTurnError(message, extras = {}) {
14
+ const error = new Error(message);
15
+ for (const [key, value] of Object.entries(extras)) {
16
+ error[key] = value;
17
+ }
18
+ return error;
19
+ }
20
+ function buildEmptyTurnResult() {
21
+ return {
22
+ text: "",
23
+ usage: null,
24
+ items: [],
25
+ events: [],
26
+ };
27
+ }
28
+ function normalizeKimiBackend(backend) {
29
+ const normalized = String(backend || "").trim().toLowerCase();
30
+ if (normalized === "kimi-cli" || normalized === "kimi-code") {
31
+ return "kimi";
32
+ }
33
+ return normalized || "kimi";
34
+ }
35
+ function injectJsonSchemaPrompt(promptText, jsonSchema) {
36
+ const schemaText = typeof jsonSchema === "string" ? jsonSchema : JSON.stringify(jsonSchema, null, 2);
37
+ return `You must respond with valid JSON that strictly conforms to the following JSON Schema. Do not include any markdown formatting or explanation outside the JSON object.
38
+
39
+ JSON Schema:
40
+ ${schemaText}
41
+
42
+ ${promptText}`;
43
+ }
44
+ function buildHistoryPrompt(history, promptText) {
45
+ let effectivePrompt = String(promptText || "").trim();
46
+ if (!effectivePrompt) {
47
+ return "";
48
+ }
49
+ const historyText = Array.isArray(history)
50
+ ? history
51
+ .map((item) => {
52
+ const role = String(item?.role || "").toLowerCase() === "assistant" ? "Assistant" : "User";
53
+ const content = String(item?.content || "").trim();
54
+ return content ? `${role}: ${content}` : "";
55
+ })
56
+ .filter(Boolean)
57
+ .join("\n\n")
58
+ : "";
59
+ if (!historyText) {
60
+ return effectivePrompt;
61
+ }
62
+ return [
63
+ "Continue the existing conversation with this history.",
64
+ "",
65
+ historyText,
66
+ "",
67
+ `User: ${effectivePrompt}`,
68
+ ].join("\n");
69
+ }
70
+ function statusLineForPhase(phase) {
71
+ switch (phase) {
72
+ case "turn_started":
73
+ return "Kimi is working on it";
74
+ case "reasoning":
75
+ return "Kimi is thinking";
76
+ case "command_execution":
77
+ return "Kimi is calling a tool";
78
+ case "message_aggregation":
79
+ return "Kimi is writing the reply";
80
+ case "turn_completed":
81
+ return "Kimi finished";
82
+ case "turn_failed":
83
+ return "Kimi failed";
84
+ default:
85
+ return "Kimi is working";
86
+ }
87
+ }
88
+ function filterKimiPrintBaseArgs(args) {
89
+ const filtered = [];
90
+ let skipNext = false;
91
+ for (const rawArg of Array.isArray(args) ? args : []) {
92
+ const arg = String(rawArg || "");
93
+ if (!arg) {
94
+ continue;
95
+ }
96
+ if (skipNext) {
97
+ skipNext = false;
98
+ continue;
99
+ }
100
+ if (arg === "--wire" || arg === "--print" || arg === "--final-message-only") {
101
+ continue;
102
+ }
103
+ if (arg === "--input-format" ||
104
+ arg === "--output-format" ||
105
+ arg === "--prompt" ||
106
+ arg === "--command" ||
107
+ arg === "--session" ||
108
+ arg === "--work-dir" ||
109
+ arg === "--model") {
110
+ skipNext = true;
111
+ continue;
112
+ }
113
+ if (arg.startsWith("--input-format=") ||
114
+ arg.startsWith("--output-format=") ||
115
+ arg.startsWith("--prompt=") ||
116
+ arg.startsWith("--command=") ||
117
+ arg.startsWith("--session=") ||
118
+ arg.startsWith("--work-dir=") ||
119
+ arg.startsWith("--model=")) {
120
+ continue;
121
+ }
122
+ filtered.push(arg);
123
+ }
124
+ return filtered;
125
+ }
126
+ function normalizeTextContent(content) {
127
+ if (typeof content === "string") {
128
+ return content;
129
+ }
130
+ if (Array.isArray(content)) {
131
+ return content
132
+ .map((part) => (part?.type === "text" && typeof part.text === "string" ? part.text : ""))
133
+ .filter(Boolean)
134
+ .join("");
135
+ }
136
+ return "";
137
+ }
138
+ function guessMimeType(filePath) {
139
+ const ext = path.extname(String(filePath || "")).toLowerCase();
140
+ switch (ext) {
141
+ case ".png":
142
+ return "image/png";
143
+ case ".jpg":
144
+ case ".jpeg":
145
+ return "image/jpeg";
146
+ case ".gif":
147
+ return "image/gif";
148
+ case ".webp":
149
+ return "image/webp";
150
+ case ".bmp":
151
+ return "image/bmp";
152
+ case ".svg":
153
+ return "image/svg+xml";
154
+ default:
155
+ return "application/octet-stream";
156
+ }
157
+ }
158
+ function filePathToDataUri(filePath) {
159
+ const buffer = fs.readFileSync(filePath);
160
+ const mimeType = guessMimeType(filePath);
161
+ return `data:${mimeType};base64,${buffer.toString("base64")}`;
162
+ }
163
+ export class KimiPrintSession extends EventEmitter {
164
+ constructor(backend, options = {}) {
165
+ super();
166
+ this.backend = normalizeKimiBackend(backend);
167
+ this.options = options;
168
+ this.logger = normalizeLogger(options.logger);
169
+ this.cwd =
170
+ typeof options.cwd === "string" && options.cwd.trim()
171
+ ? options.cwd.trim()
172
+ : process.cwd();
173
+ this.resumeSessionId = typeof options.resumeSessionId === "string" ? options.resumeSessionId.trim() : "";
174
+ this.sessionId = this.resumeSessionId || randomUUID();
175
+ this.sessionInfo = {
176
+ backend: this.backend,
177
+ sessionId: this.sessionId,
178
+ model: typeof options.model === "string" && options.model.trim()
179
+ ? options.model.trim()
180
+ : this.backend,
181
+ };
182
+ this.history = Array.isArray(options.initialHistory) ? [...options.initialHistory] : [];
183
+ this.pendingHistorySeed = this.history.length > 0;
184
+ this.closeRequested = false;
185
+ this.closed = false;
186
+ this.currentTurn = null;
187
+ this.currentTurnStatus = null;
188
+ this.sessionAnnounced = false;
189
+ this.sessionMessageHandler = null;
190
+ this.workingStatusHandler = null;
191
+ this.activeReplyTarget = "";
192
+ this.lastReplyTarget = "";
193
+ this.turnDeadlineMs = getBoundedEnvInt("CONDUCTOR_TURN_DEADLINE_MS", DEFAULT_TURN_DEADLINE_MS, MIN_TURN_DEADLINE_MS, MAX_TURN_DEADLINE_MS);
194
+ const envConfig = loadEnvConfig(options.configFile);
195
+ const proxyEnv = proxyToEnv(envConfig);
196
+ this.env = {
197
+ ...(envConfig && typeof envConfig === "object" ? envConfig : {}),
198
+ ...proxyEnv,
199
+ ...(options.env && typeof options.env === "object" ? options.env : {}),
200
+ };
201
+ const commandLine = process.env.CONDUCTOR_KIMI_COMMAND ||
202
+ options.commandLine ||
203
+ process.env.CONDUCTOR_CLI_COMMAND ||
204
+ DEFAULT_KIMI_COMMAND;
205
+ const { command, args } = parseCommandParts(commandLine);
206
+ if (!command) {
207
+ throw new Error("Invalid kimi print command");
208
+ }
209
+ this.command = command;
210
+ this.baseArgs = filterKimiPrintBaseArgs(args);
211
+ }
212
+ writeLog(message) {
213
+ emitLog(this.logger, message);
214
+ }
215
+ trace(message) {
216
+ this.writeLog(`[${this.backend}] [kimi-print] ${message}`);
217
+ }
218
+ get threadId() {
219
+ return this.sessionId;
220
+ }
221
+ get threadOptions() {
222
+ return {
223
+ model: this.sessionInfo?.model ||
224
+ (typeof this.options.model === "string" && this.options.model.trim()
225
+ ? this.options.model.trim()
226
+ : this.backend),
227
+ };
228
+ }
229
+ buildManualResumeCommand() {
230
+ return `kimi --work-dir ${this.cwd} --session ${this.sessionId}`;
231
+ }
232
+ getSnapshot() {
233
+ return {
234
+ backend: this.backend,
235
+ provider: KIMI_PRINT_PROVIDER_VARIANT,
236
+ cwd: this.cwd,
237
+ sessionId: this.sessionId || undefined,
238
+ sessionInfo: this.getSessionInfo(),
239
+ useSessionFileReplyStream: this.usesSessionFileReplyStream(),
240
+ resumeReady: Boolean(this.sessionId),
241
+ manualResume: this.sessionId
242
+ ? {
243
+ ready: true,
244
+ command: this.buildManualResumeCommand(),
245
+ }
246
+ : null,
247
+ currentTurnStatus: this.getCurrentTurnStatus(),
248
+ pid: this.currentTurn?.child?.pid || undefined,
249
+ };
250
+ }
251
+ getSessionInfo() {
252
+ return this.sessionInfo ? { ...this.sessionInfo } : null;
253
+ }
254
+ getCurrentTurnStatus() {
255
+ return this.currentTurnStatus ? { ...this.currentTurnStatus } : null;
256
+ }
257
+ async ensureSessionInfo() {
258
+ this.announceSession();
259
+ return this.getSessionInfo();
260
+ }
261
+ async getSessionUsageSummary() {
262
+ return {
263
+ sessionId: this.sessionId || undefined,
264
+ sessionFilePath: undefined,
265
+ tokenUsagePercent: undefined,
266
+ contextUsagePercent: undefined,
267
+ tokenUsage: null,
268
+ rateLimits: null,
269
+ manualResume: this.sessionId
270
+ ? {
271
+ ready: true,
272
+ command: this.buildManualResumeCommand(),
273
+ }
274
+ : null,
275
+ };
276
+ }
277
+ usesSessionFileReplyStream() {
278
+ return true;
279
+ }
280
+ setSessionMessageHandler(handler) {
281
+ this.sessionMessageHandler = typeof handler === "function" ? handler : null;
282
+ }
283
+ setWorkingStatusHandler(handler) {
284
+ this.workingStatusHandler = typeof handler === "function" ? handler : null;
285
+ }
286
+ setSessionReplyTarget(replyTo) {
287
+ const normalizedReplyTo = typeof replyTo === "string" ? replyTo.trim() : "";
288
+ this.activeReplyTarget = normalizedReplyTo;
289
+ if (normalizedReplyTo) {
290
+ this.lastReplyTarget = normalizedReplyTo;
291
+ }
292
+ }
293
+ getCurrentReplyTarget() {
294
+ return this.activeReplyTarget || this.lastReplyTarget || undefined;
295
+ }
296
+ announceSession() {
297
+ if (this.sessionAnnounced) {
298
+ return;
299
+ }
300
+ this.sessionAnnounced = true;
301
+ this.emit("session", this.getSessionInfo());
302
+ }
303
+ createSessionClosedError() {
304
+ return createTurnError("Kimi print session closed", {
305
+ reason: "session_closed",
306
+ });
307
+ }
308
+ updateCurrentTurnStatus(payload) {
309
+ const updatedAtMs = Date.now();
310
+ this.currentTurnStatus = {
311
+ source: KIMI_PRINT_PROVIDER_VARIANT,
312
+ replyTo: this.getCurrentReplyTarget(),
313
+ thread_id: this.sessionId || undefined,
314
+ session_id: this.sessionId || undefined,
315
+ ...payload,
316
+ updated_at: new Date(updatedAtMs).toISOString(),
317
+ };
318
+ }
319
+ async emitWorkingStatus(payload, onProgress = null) {
320
+ this.updateCurrentTurnStatus(payload);
321
+ const normalized = this.getCurrentTurnStatus();
322
+ if (typeof onProgress === "function") {
323
+ await onProgress(normalized);
324
+ }
325
+ if (typeof this.workingStatusHandler === "function") {
326
+ await this.workingStatusHandler(normalized);
327
+ }
328
+ this.emit("working_status", normalized);
329
+ return normalized;
330
+ }
331
+ async emitAssistantMessage(text) {
332
+ const normalizedText = typeof text === "string" ? text : "";
333
+ if (!normalizedText) {
334
+ return;
335
+ }
336
+ const payload = {
337
+ text: normalizedText,
338
+ preserveWhitespace: true,
339
+ replyTo: this.getCurrentReplyTarget(),
340
+ sessionId: this.sessionId || undefined,
341
+ backend: this.backend,
342
+ provider: KIMI_PRINT_PROVIDER_VARIANT,
343
+ };
344
+ if (typeof this.sessionMessageHandler === "function") {
345
+ await this.sessionMessageHandler(payload);
346
+ }
347
+ this.emit("assistant_message", payload);
348
+ }
349
+ buildPrompt(promptText) {
350
+ const normalizedPrompt = String(promptText || "").trim();
351
+ if (!normalizedPrompt) {
352
+ return "";
353
+ }
354
+ if (!this.pendingHistorySeed) {
355
+ return normalizedPrompt;
356
+ }
357
+ this.pendingHistorySeed = false;
358
+ return buildHistoryPrompt(this.history, normalizedPrompt);
359
+ }
360
+ buildPrintArgs() {
361
+ const args = [...this.baseArgs];
362
+ args.push("--print");
363
+ args.push("--input-format=stream-json");
364
+ args.push("--output-format=stream-json");
365
+ args.push(`--work-dir=${this.cwd}`);
366
+ if (this.sessionId) {
367
+ args.push(`--session=${this.sessionId}`);
368
+ }
369
+ if (typeof this.options.model === "string" && this.options.model.trim()) {
370
+ args.push(`--model=${this.options.model.trim()}`);
371
+ }
372
+ return args;
373
+ }
374
+ buildUserMessage(promptText, { useInitialImages = false } = {}) {
375
+ const images = useInitialImages && Array.isArray(this.options.initialImages)
376
+ ? this.options.initialImages.filter((item) => typeof item === "string" && item.trim())
377
+ : [];
378
+ if (images.length === 0) {
379
+ return {
380
+ role: "user",
381
+ content: promptText,
382
+ };
383
+ }
384
+ const content = [];
385
+ if (promptText) {
386
+ content.push({ type: "text", text: promptText });
387
+ }
388
+ for (const imagePath of images) {
389
+ content.push({
390
+ type: "image_url",
391
+ image_url: {
392
+ url: filePathToDataUri(imagePath),
393
+ },
394
+ });
395
+ }
396
+ return {
397
+ role: "user",
398
+ content,
399
+ };
400
+ }
401
+ maybeEmitAuthRequired(stderrTail) {
402
+ const lastMessage = Array.isArray(stderrTail) ? String(stderrTail.filter(Boolean).at(-1) || "") : "";
403
+ const normalized = lastMessage.toLowerCase();
404
+ if (!normalized.includes("login") &&
405
+ !normalized.includes("auth") &&
406
+ !normalized.includes("api key") &&
407
+ !normalized.includes("credential") &&
408
+ !normalized.includes("llm is not set")) {
409
+ return;
410
+ }
411
+ this.emit("auth_required", {
412
+ reason: "login_required",
413
+ message: lastMessage || "Kimi authentication required",
414
+ });
415
+ }
416
+ async runTurn(promptText, { useInitialImages = false, onProgress = null, jsonSchema = null } = {}) {
417
+ if (this.closeRequested || this.closed) {
418
+ throw this.createSessionClosedError();
419
+ }
420
+ if (this.currentTurn) {
421
+ throw createTurnError("Kimi print turn already running", {
422
+ reason: "turn_already_running",
423
+ });
424
+ }
425
+ let effectivePrompt = this.buildPrompt(promptText);
426
+ const imagePaths = useInitialImages && Array.isArray(this.options.initialImages)
427
+ ? this.options.initialImages.filter((item) => typeof item === "string" && item.trim())
428
+ : [];
429
+ if (jsonSchema && typeof jsonSchema === "object") {
430
+ const promptWithSchema = effectivePrompt || (imagePaths.length > 0 ? "Analyze the attached images." : "");
431
+ if (promptWithSchema) {
432
+ effectivePrompt = injectJsonSchemaPrompt(promptWithSchema, jsonSchema);
433
+ }
434
+ }
435
+ if (!effectivePrompt && imagePaths.length === 0) {
436
+ return buildEmptyTurnResult();
437
+ }
438
+ this.announceSession();
439
+ this.history.push({ role: "user", content: String(promptText || "") });
440
+ const currentTurn = {
441
+ child: null,
442
+ fullText: "",
443
+ items: [],
444
+ stderrTail: [],
445
+ settled: false,
446
+ };
447
+ this.currentTurn = currentTurn;
448
+ try {
449
+ await this.emitWorkingStatus({
450
+ phase: "turn_started",
451
+ reply_in_progress: true,
452
+ status_line: statusLineForPhase("turn_started"),
453
+ }, onProgress);
454
+ const args = this.buildPrintArgs();
455
+ this.trace(`spawn ${[this.command, ...args].join(" ")}`);
456
+ const result = await new Promise((resolve, reject) => {
457
+ const child = spawn(this.command, args, {
458
+ cwd: this.cwd,
459
+ env: {
460
+ ...process.env,
461
+ PWD: this.cwd,
462
+ ...this.env,
463
+ },
464
+ stdio: ["pipe", "pipe", "pipe"],
465
+ });
466
+ currentTurn.child = child;
467
+ const stdoutReader = readline.createInterface({ input: child.stdout });
468
+ const stderrReader = readline.createInterface({ input: child.stderr });
469
+ let timeoutId = null;
470
+ const settle = (error, value = null) => {
471
+ if (currentTurn.settled) {
472
+ return;
473
+ }
474
+ currentTurn.settled = true;
475
+ if (timeoutId) {
476
+ clearTimeout(timeoutId);
477
+ }
478
+ stdoutReader.close();
479
+ stderrReader.close();
480
+ if (error) {
481
+ reject(error);
482
+ return;
483
+ }
484
+ resolve(value);
485
+ };
486
+ timeoutId = setTimeout(() => {
487
+ try {
488
+ child.kill("SIGTERM");
489
+ }
490
+ catch {
491
+ // ignore
492
+ }
493
+ settle(createTurnError("Kimi print turn timed out", {
494
+ reason: "turn_timeout",
495
+ }));
496
+ }, this.turnDeadlineMs);
497
+ stdoutReader.on("line", (line) => {
498
+ const normalizedLine = String(line || "").trim();
499
+ if (!normalizedLine) {
500
+ return;
501
+ }
502
+ let payload = null;
503
+ try {
504
+ payload = JSON.parse(normalizedLine);
505
+ }
506
+ catch {
507
+ payload = { role: "raw", content: normalizedLine };
508
+ }
509
+ currentTurn.items.push(payload);
510
+ const role = String(payload?.role || "").trim().toLowerCase();
511
+ if (role === "assistant") {
512
+ const assistantText = normalizeTextContent(payload.content);
513
+ if (Array.isArray(payload?.tool_calls) && payload.tool_calls.length > 0) {
514
+ void this.emitWorkingStatus({
515
+ phase: "command_execution",
516
+ reply_in_progress: true,
517
+ status_line: statusLineForPhase("command_execution"),
518
+ }, onProgress);
519
+ }
520
+ else {
521
+ void this.emitWorkingStatus({
522
+ phase: "message_aggregation",
523
+ reply_in_progress: true,
524
+ status_line: statusLineForPhase("message_aggregation"),
525
+ }, onProgress);
526
+ }
527
+ if (assistantText) {
528
+ currentTurn.fullText += assistantText;
529
+ void this.emitAssistantMessage(assistantText);
530
+ }
531
+ return;
532
+ }
533
+ if (role === "tool") {
534
+ void this.emitWorkingStatus({
535
+ phase: "command_execution",
536
+ reply_in_progress: true,
537
+ status_line: statusLineForPhase("command_execution"),
538
+ }, onProgress);
539
+ }
540
+ });
541
+ stderrReader.on("line", (line) => {
542
+ const normalizedLine = String(line || "");
543
+ if (!normalizedLine.trim()) {
544
+ return;
545
+ }
546
+ currentTurn.stderrTail.push(normalizedLine);
547
+ if (currentTurn.stderrTail.length > 20) {
548
+ currentTurn.stderrTail.shift();
549
+ }
550
+ this.writeLog(`[kimi-print] stderr ${sanitizeForLog(normalizedLine, 300)}`);
551
+ });
552
+ child.on("error", (error) => {
553
+ settle(error);
554
+ });
555
+ child.on("exit", (code, signal) => {
556
+ if (this.closeRequested) {
557
+ settle(this.createSessionClosedError());
558
+ return;
559
+ }
560
+ if (code !== 0) {
561
+ this.maybeEmitAuthRequired(currentTurn.stderrTail);
562
+ const stderrSummary = sanitizeForLog(currentTurn.stderrTail.filter(Boolean).at(-1), 200);
563
+ settle(createTurnError(stderrSummary ? `Kimi print failed: ${stderrSummary}` : "Kimi print failed", {
564
+ reason: code === 75 ? "retryable_turn_failed" : "turn_failed",
565
+ retryable: code === 75,
566
+ code,
567
+ signal,
568
+ stderr: [...currentTurn.stderrTail],
569
+ }));
570
+ return;
571
+ }
572
+ settle(null, {
573
+ text: currentTurn.fullText,
574
+ items: [...currentTurn.items],
575
+ });
576
+ });
577
+ const userMessage = this.buildUserMessage(effectivePrompt, { useInitialImages });
578
+ child.stdin.write(`${JSON.stringify(userMessage)}\n`);
579
+ child.stdin.end();
580
+ });
581
+ if (result?.text) {
582
+ this.history.push({ role: "assistant", content: result.text });
583
+ }
584
+ this.activeReplyTarget = "";
585
+ await this.emitWorkingStatus({
586
+ phase: "turn_completed",
587
+ reply_in_progress: false,
588
+ status_line: statusLineForPhase("turn_completed"),
589
+ }, onProgress);
590
+ return {
591
+ text: result?.text || "",
592
+ usage: null,
593
+ items: result?.items || [],
594
+ events: result?.items || [],
595
+ provider: this.backend,
596
+ metadata: {
597
+ source: KIMI_PRINT_PROVIDER_VARIANT,
598
+ sessionId: this.sessionId || undefined,
599
+ },
600
+ };
601
+ }
602
+ catch (error) {
603
+ if (!this.closeRequested && error?.reason !== "session_closed") {
604
+ await this.emitWorkingStatus({
605
+ phase: "turn_failed",
606
+ reply_in_progress: false,
607
+ status_line: statusLineForPhase("turn_failed"),
608
+ status_done_line: error?.message || "Kimi print failed",
609
+ }, onProgress);
610
+ }
611
+ throw error;
612
+ }
613
+ finally {
614
+ this.currentTurn = null;
615
+ }
616
+ }
617
+ async close() {
618
+ if (this.closed) {
619
+ return;
620
+ }
621
+ this.closeRequested = true;
622
+ this.closed = true;
623
+ const child = this.currentTurn?.child;
624
+ if (child) {
625
+ try {
626
+ child.kill("SIGTERM");
627
+ }
628
+ catch {
629
+ // ignore
630
+ }
631
+ }
632
+ }
633
+ }
@@ -163,10 +163,11 @@ export class OpencodeSdkSession extends EventEmitter<[never]> {
163
163
  handleOpencodeEvent(event: any): Promise<void>;
164
164
  handleTransportFailure(error: any): void;
165
165
  handleTransportExit(payload: any): void;
166
- interruptCurrentTurn(): Promise<void>;
167
- runTurn(promptText: any, { useInitialImages, onProgress }?: {
166
+ interruptCurrentTurn(): Promise<boolean>;
167
+ runTurn(promptText: any, { useInitialImages, onProgress, jsonSchema }?: {
168
168
  useInitialImages?: boolean | undefined;
169
169
  onProgress?: null | undefined;
170
+ jsonSchema?: null | undefined;
170
171
  }): Promise<{
171
172
  text: string;
172
173
  usage: null;
@@ -1119,7 +1119,7 @@ export class OpencodeSdkSession extends EventEmitter {
1119
1119
  async interruptCurrentTurn() {
1120
1120
  const currentTurn = this.currentTurn;
1121
1121
  if (!currentTurn || !this.client?.session || !this.sessionId) {
1122
- return;
1122
+ return false;
1123
1123
  }
1124
1124
  try {
1125
1125
  currentTurn.abortController?.abort?.();
@@ -1129,12 +1129,14 @@ export class OpencodeSdkSession extends EventEmitter {
1129
1129
  }
1130
1130
  try {
1131
1131
  await this.client.session.abort({ sessionID: this.sessionId }, { throwOnError: true, responseStyle: "data" });
1132
+ return true;
1132
1133
  }
1133
1134
  catch {
1134
1135
  // best effort
1135
1136
  }
1137
+ return true;
1136
1138
  }
1137
- async runTurn(promptText, { useInitialImages = false, onProgress = null } = {}) {
1139
+ async runTurn(promptText, { useInitialImages = false, onProgress = null, jsonSchema = null } = {}) {
1138
1140
  if (this.closeRequested) {
1139
1141
  throw this.createSessionClosedError();
1140
1142
  }
@@ -1211,6 +1213,9 @@ export class OpencodeSdkSession extends EventEmitter {
1211
1213
  variant: typeof this.options.variant === "string" && this.options.variant.trim()
1212
1214
  ? this.options.variant.trim()
1213
1215
  : undefined,
1216
+ format: jsonSchema && typeof jsonSchema === "object"
1217
+ ? { type: "json_schema", schema: jsonSchema }
1218
+ : undefined,
1214
1219
  parts: [
1215
1220
  {
1216
1221
  type: "text",