@shareai-lab/kode-sdk 2.7.2 → 2.7.4

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,325 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OpenSandbox = void 0;
4
+ const opensandbox_1 = require("@alibaba-group/opensandbox");
5
+ const logger_1 = require("../../utils/logger");
6
+ const opensandbox_fs_1 = require("./opensandbox-fs");
7
+ class OpenSandbox {
8
+ constructor(options) {
9
+ this.kind = 'opensandbox';
10
+ this.sandbox = null;
11
+ this.watchers = new Map();
12
+ this.options = { ...options };
13
+ this.workDir = options.workDir || '/workspace';
14
+ this.fs = new opensandbox_fs_1.OpenSandboxFS(this);
15
+ this.watchMode = options.watch?.mode || 'polling';
16
+ this.pollIntervalMs = Math.max(100, options.watch?.pollIntervalMs ?? 1000);
17
+ this.disposeAction = options.lifecycle?.disposeAction || 'kill';
18
+ }
19
+ async init() {
20
+ if (this.sandbox)
21
+ return;
22
+ const connectionConfig = this.buildConnectionConfig();
23
+ if (this.options.sandboxId) {
24
+ const connectOptions = {
25
+ sandboxId: this.options.sandboxId,
26
+ connectionConfig,
27
+ skipHealthCheck: this.options.skipHealthCheck,
28
+ readyTimeoutSeconds: this.options.readyTimeoutSeconds,
29
+ healthCheckPollingInterval: this.options.healthCheckPollingInterval,
30
+ };
31
+ this.sandbox = await opensandbox_1.Sandbox.connect(connectOptions);
32
+ }
33
+ else {
34
+ const createOptions = {
35
+ connectionConfig,
36
+ image: this.options.image || this.options.template || 'ubuntu',
37
+ timeoutSeconds: Math.max(1, Math.ceil((this.options.timeoutMs ?? 10 * 60 * 1000) / 1000)),
38
+ env: this.options.env,
39
+ metadata: this.options.metadata,
40
+ resource: this.options.resource,
41
+ networkPolicy: this.options.networkPolicy,
42
+ skipHealthCheck: this.options.skipHealthCheck,
43
+ readyTimeoutSeconds: this.options.readyTimeoutSeconds,
44
+ healthCheckPollingInterval: this.options.healthCheckPollingInterval,
45
+ };
46
+ this.sandbox = await opensandbox_1.Sandbox.create(createOptions);
47
+ }
48
+ // Persist resolved sandbox id for Agent resume metadata.
49
+ this.options.sandboxId = this.sandbox.id;
50
+ // Best-effort workdir bootstrap.
51
+ if (this.workDir && this.workDir !== '/') {
52
+ await this.sandbox.commands
53
+ .run(`mkdir -p ${quoteShell(this.workDir)}`, {
54
+ workingDirectory: '/',
55
+ timeoutSeconds: 10,
56
+ })
57
+ .catch(() => undefined);
58
+ }
59
+ }
60
+ getOpenSandbox() {
61
+ if (!this.sandbox) {
62
+ throw new Error('OpenSandbox not initialized. Call init() first.');
63
+ }
64
+ return this.sandbox;
65
+ }
66
+ getSandboxId() {
67
+ return this.sandbox?.id || this.options.sandboxId;
68
+ }
69
+ async isRunning() {
70
+ try {
71
+ const info = await this.getOpenSandbox().getInfo();
72
+ const state = String(info?.status?.state || '').toLowerCase();
73
+ return state === 'running';
74
+ }
75
+ catch {
76
+ return false;
77
+ }
78
+ }
79
+ async exec(cmd, opts) {
80
+ const sandbox = this.getOpenSandbox();
81
+ const timeoutMs = this.resolveExecTimeoutMs(opts);
82
+ const timeoutSeconds = Math.max(1, Math.ceil(timeoutMs / 1000));
83
+ try {
84
+ const execution = await sandbox.commands.run(cmd, {
85
+ workingDirectory: this.workDir,
86
+ timeoutSeconds,
87
+ });
88
+ const stdout = execution.logs.stdout.map((m) => m.text).join('');
89
+ let stderr = execution.logs.stderr.map((m) => m.text).join('');
90
+ let code = execution.error ? 1 : 0;
91
+ if (execution.id) {
92
+ try {
93
+ const status = await sandbox.commands.getCommandStatus(execution.id);
94
+ if (typeof status.exitCode === 'number') {
95
+ code = status.exitCode;
96
+ }
97
+ else if (status.running === false && status.error) {
98
+ code = 1;
99
+ }
100
+ }
101
+ catch {
102
+ // keep fallback code when status API is unavailable
103
+ }
104
+ }
105
+ if (execution.error && !stderr) {
106
+ const traces = Array.isArray(execution.error.traceback) ? execution.error.traceback.join('\n') : '';
107
+ stderr = [execution.error.name, execution.error.value, traces].filter(Boolean).join('\n');
108
+ }
109
+ return { code, stdout, stderr };
110
+ }
111
+ catch (error) {
112
+ return {
113
+ code: 1,
114
+ stdout: '',
115
+ stderr: error?.message || String(error),
116
+ };
117
+ }
118
+ }
119
+ async watchFiles(paths, listener) {
120
+ if (this.watchMode === 'off') {
121
+ return `watch-disabled-${Date.now()}`;
122
+ }
123
+ const id = `opensandbox-watch-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
124
+ const resolved = Array.from(new Set(paths.map((p) => this.fs.resolve(p))));
125
+ if (this.watchMode === 'native') {
126
+ const nativeStarted = await this.startNativeWatcher(id, resolved, listener);
127
+ if (nativeStarted) {
128
+ return id;
129
+ }
130
+ logger_1.logger.warn('[OpenSandbox] native watch unavailable, falling back to polling mode.');
131
+ }
132
+ await this.startPollingWatcher(id, resolved, listener);
133
+ return id;
134
+ }
135
+ unwatchFiles(id) {
136
+ const watcher = this.watchers.get(id);
137
+ if (!watcher)
138
+ return;
139
+ if (watcher.kind === 'polling') {
140
+ clearInterval(watcher.timer);
141
+ }
142
+ else {
143
+ watcher.abortController.abort();
144
+ }
145
+ this.watchers.delete(id);
146
+ }
147
+ async dispose() {
148
+ for (const id of Array.from(this.watchers.keys())) {
149
+ this.unwatchFiles(id);
150
+ }
151
+ if (!this.sandbox)
152
+ return;
153
+ let disposeError;
154
+ const sandbox = this.sandbox;
155
+ this.sandbox = null;
156
+ if (this.disposeAction === 'kill') {
157
+ try {
158
+ await sandbox.kill();
159
+ }
160
+ catch (error) {
161
+ disposeError = error;
162
+ }
163
+ }
164
+ try {
165
+ await sandbox.close();
166
+ }
167
+ catch (error) {
168
+ disposeError = disposeError || error;
169
+ }
170
+ if (disposeError) {
171
+ throw disposeError;
172
+ }
173
+ }
174
+ buildConnectionConfig() {
175
+ const config = {
176
+ apiKey: this.options.apiKey,
177
+ domain: this.options.endpoint || this.options.domain,
178
+ protocol: this.options.protocol,
179
+ requestTimeoutSeconds: this.options.requestTimeoutSeconds,
180
+ useServerProxy: this.options.useServerProxy ?? false,
181
+ };
182
+ return new opensandbox_1.ConnectionConfig(config);
183
+ }
184
+ async pollWatcher(id, listener) {
185
+ const watcher = this.watchers.get(id);
186
+ if (!watcher || watcher.kind !== 'polling' || watcher.polling)
187
+ return;
188
+ watcher.polling = true;
189
+ try {
190
+ do {
191
+ watcher.pending = false;
192
+ for (const path of watcher.paths) {
193
+ if (!this.watchers.has(id)) {
194
+ return;
195
+ }
196
+ const current = await this.safeMtime(path);
197
+ const previous = watcher.lastMtimes.get(path);
198
+ watcher.lastMtimes.set(path, current);
199
+ if (previous === undefined && current === undefined)
200
+ continue;
201
+ if (previous === current)
202
+ continue;
203
+ listener({ path, mtimeMs: current ?? Date.now() });
204
+ }
205
+ } while (this.watchers.has(id) && watcher.pending);
206
+ }
207
+ finally {
208
+ if (this.watchers.get(id) === watcher) {
209
+ watcher.polling = false;
210
+ }
211
+ }
212
+ }
213
+ async safeMtime(path) {
214
+ try {
215
+ const stat = await this.fs.stat(path);
216
+ return stat.mtimeMs;
217
+ }
218
+ catch {
219
+ return undefined;
220
+ }
221
+ }
222
+ resolveExecTimeoutMs(opts) {
223
+ return opts?.timeoutMs ?? this.options.execTimeoutMs ?? this.options.timeoutMs ?? 120000;
224
+ }
225
+ async startPollingWatcher(id, paths, listener) {
226
+ const lastMtimes = new Map();
227
+ for (const p of paths) {
228
+ lastMtimes.set(p, await this.safeMtime(p));
229
+ }
230
+ const watcher = {
231
+ kind: 'polling',
232
+ timer: setInterval(() => {
233
+ const current = this.watchers.get(id);
234
+ if (!current || current.kind !== 'polling')
235
+ return;
236
+ current.pending = true;
237
+ if (!current.polling) {
238
+ void this.pollWatcher(id, listener);
239
+ }
240
+ }, this.pollIntervalMs),
241
+ paths,
242
+ lastMtimes,
243
+ polling: false,
244
+ pending: true,
245
+ };
246
+ this.watchers.set(id, watcher);
247
+ void this.pollWatcher(id, listener);
248
+ }
249
+ async startNativeWatcher(id, paths, listener) {
250
+ const probe = await this.exec('command -v inotifywait >/dev/null 2>&1 && echo __KODE_INOTIFY_READY__', {
251
+ timeoutMs: 5000,
252
+ });
253
+ if (probe.code !== 0 || !probe.stdout.includes('__KODE_INOTIFY_READY__')) {
254
+ return false;
255
+ }
256
+ const sandbox = this.getOpenSandbox();
257
+ const abortController = new AbortController();
258
+ const nativeWatchCommand = buildNativeWatchCommand(paths);
259
+ let stdoutBuffer = '';
260
+ const streamTask = (async () => {
261
+ try {
262
+ for await (const event of sandbox.commands.runStream(nativeWatchCommand, { workingDirectory: this.workDir }, abortController.signal)) {
263
+ if (abortController.signal.aborted) {
264
+ break;
265
+ }
266
+ if (event.type !== 'stdout' || typeof event.text !== 'string') {
267
+ continue;
268
+ }
269
+ stdoutBuffer += event.text;
270
+ let lineBreak = stdoutBuffer.indexOf('\n');
271
+ while (lineBreak >= 0) {
272
+ const line = stdoutBuffer.slice(0, lineBreak).trim();
273
+ stdoutBuffer = stdoutBuffer.slice(lineBreak + 1);
274
+ if (line) {
275
+ listener({ path: line, mtimeMs: Date.now() });
276
+ }
277
+ lineBreak = stdoutBuffer.indexOf('\n');
278
+ }
279
+ }
280
+ const tail = stdoutBuffer.trim();
281
+ if (tail) {
282
+ listener({ path: tail, mtimeMs: Date.now() });
283
+ }
284
+ }
285
+ catch (error) {
286
+ if (!abortController.signal.aborted) {
287
+ logger_1.logger.warn('[OpenSandbox] native watch stream failed, fallback to polling.', error);
288
+ }
289
+ }
290
+ finally {
291
+ const current = this.watchers.get(id);
292
+ if (!current || current.kind !== 'native' || current.abortController !== abortController) {
293
+ return;
294
+ }
295
+ this.watchers.delete(id);
296
+ if (!abortController.signal.aborted) {
297
+ try {
298
+ await this.startPollingWatcher(id, paths, listener);
299
+ logger_1.logger.warn('[OpenSandbox] native watch stopped, switched to polling mode.');
300
+ }
301
+ catch (error) {
302
+ logger_1.logger.warn('[OpenSandbox] failed to start polling fallback after native watch exit.', error);
303
+ }
304
+ }
305
+ }
306
+ })();
307
+ const watcher = {
308
+ kind: 'native',
309
+ paths,
310
+ abortController,
311
+ streamTask,
312
+ };
313
+ this.watchers.set(id, watcher);
314
+ return true;
315
+ }
316
+ }
317
+ exports.OpenSandbox = OpenSandbox;
318
+ function quoteShell(value) {
319
+ return `'${value.replace(/'/g, `'\\''`)}'`;
320
+ }
321
+ function buildNativeWatchCommand(paths) {
322
+ const targets = paths.map((p) => quoteShell(p)).join(' ');
323
+ const script = `exec inotifywait -m -e modify,create,delete,move --format '%w%f' -- ${targets}`;
324
+ return `sh -lc ${quoteShell(script)}`;
325
+ }
@@ -0,0 +1,30 @@
1
+ export type OpenSandboxWatchMode = 'native' | 'polling' | 'off';
2
+ export interface OpenSandboxOptions {
3
+ kind: 'opensandbox';
4
+ apiKey?: string;
5
+ endpoint?: string;
6
+ domain?: string;
7
+ protocol?: 'http' | 'https';
8
+ sandboxId?: string;
9
+ image?: string;
10
+ template?: string;
11
+ workDir?: string;
12
+ timeoutMs?: number;
13
+ execTimeoutMs?: number;
14
+ requestTimeoutSeconds?: number;
15
+ useServerProxy?: boolean;
16
+ env?: Record<string, string>;
17
+ metadata?: Record<string, string>;
18
+ resource?: Record<string, string>;
19
+ networkPolicy?: Record<string, any>;
20
+ skipHealthCheck?: boolean;
21
+ readyTimeoutSeconds?: number;
22
+ healthCheckPollingInterval?: number;
23
+ watch?: {
24
+ mode?: OpenSandboxWatchMode;
25
+ pollIntervalMs?: number;
26
+ };
27
+ lifecycle?: {
28
+ disposeAction?: 'close' | 'kill';
29
+ };
30
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -207,6 +207,10 @@ class AnthropicProvider {
207
207
  degraded = true;
208
208
  return { type: 'text', text: utils_1.AUDIO_UNSUPPORTED_TEXT };
209
209
  }
210
+ if (block.type === 'video') {
211
+ degraded = true;
212
+ return { type: 'text', text: utils_1.VIDEO_UNSUPPORTED_TEXT };
213
+ }
210
214
  if (block.type === 'file') {
211
215
  if (block.file_id) {
212
216
  return {
@@ -214,6 +218,18 @@ class AnthropicProvider {
214
218
  source: { type: 'file', file_id: block.file_id },
215
219
  };
216
220
  }
221
+ if (block.base64 && block.mime_type) {
222
+ return {
223
+ type: 'document',
224
+ source: { type: 'base64', media_type: block.mime_type, data: block.base64 },
225
+ };
226
+ }
227
+ if (block.url) {
228
+ return {
229
+ type: 'document',
230
+ source: { type: 'url', url: block.url },
231
+ };
232
+ }
217
233
  degraded = true;
218
234
  return { type: 'text', text: utils_1.FILE_UNSUPPORTED_TEXT };
219
235
  }
@@ -29,14 +29,26 @@ class GeminiProvider {
29
29
  this.thinking = options?.thinking;
30
30
  }
31
31
  async uploadFile(input) {
32
- if (input.kind !== 'file') {
32
+ // Gemini supports uploading audio, video, and file types
33
+ if (input.kind !== 'file' && input.kind !== 'audio' && input.kind !== 'video') {
33
34
  return null;
34
35
  }
35
36
  const url = new URL(`${this.baseUrl}/files`);
36
37
  url.searchParams.set('key', this.apiKey);
38
+ // Determine display name based on kind
39
+ let defaultFilename;
40
+ if (input.kind === 'audio') {
41
+ defaultFilename = 'audio.wav';
42
+ }
43
+ else if (input.kind === 'video') {
44
+ defaultFilename = 'video.mp4';
45
+ }
46
+ else {
47
+ defaultFilename = 'file.pdf';
48
+ }
37
49
  const body = {
38
50
  file: {
39
- display_name: input.filename || 'file.pdf',
51
+ display_name: input.filename || defaultFilename,
40
52
  mime_type: input.mimeType,
41
53
  },
42
54
  content: input.data.toString('base64'),
@@ -372,8 +384,24 @@ class GeminiProvider {
372
384
  }
373
385
  }
374
386
  else if (block.type === 'audio') {
375
- degraded = true;
376
- parts.push({ text: utils_1.AUDIO_UNSUPPORTED_TEXT });
387
+ const audioPart = (0, utils_1.buildGeminiAudioPart)(block);
388
+ if (audioPart) {
389
+ parts.push(audioPart);
390
+ }
391
+ else {
392
+ degraded = true;
393
+ parts.push({ text: utils_1.AUDIO_UNSUPPORTED_TEXT });
394
+ }
395
+ }
396
+ else if (block.type === 'video') {
397
+ const videoPart = (0, utils_1.buildGeminiVideoPart)(block);
398
+ if (videoPart) {
399
+ parts.push(videoPart);
400
+ }
401
+ else {
402
+ degraded = true;
403
+ parts.push({ text: utils_1.VIDEO_UNSUPPORTED_TEXT });
404
+ }
377
405
  }
378
406
  else if (block.type === 'file') {
379
407
  const filePart = (0, utils_1.buildGeminiFilePart)(block);
@@ -458,7 +458,7 @@ class OpenAIProvider {
458
458
  buildOpenAIMessages(messages, system, reasoningTransport = 'text') {
459
459
  const output = [];
460
460
  const toolCallNames = new Map();
461
- const useStructuredContent = messages.some((msg) => (0, utils_1.getMessageBlocks)(msg).some((block) => block.type === 'image' || block.type === 'audio' || block.type === 'file'));
461
+ const useStructuredContent = messages.some((msg) => (0, utils_1.getMessageBlocks)(msg).some((block) => block.type === 'image' || block.type === 'audio' || block.type === 'video' || block.type === 'file'));
462
462
  for (const msg of messages) {
463
463
  for (const block of (0, utils_1.getMessageBlocks)(msg)) {
464
464
  if (block.type === 'tool_use') {
@@ -585,8 +585,21 @@ class OpenAIProvider {
585
585
  continue;
586
586
  }
587
587
  if (block.type === 'audio') {
588
+ // OpenAI Chat Completions API supports audio via input_audio (wav/mp3 base64 only)
589
+ const audioPart = (0, utils_1.buildOpenAIAudioPart)(block);
590
+ if (audioPart) {
591
+ contentParts.push(audioPart);
592
+ }
593
+ else {
594
+ degraded = true;
595
+ appendText(utils_1.AUDIO_UNSUPPORTED_TEXT);
596
+ }
597
+ continue;
598
+ }
599
+ if (block.type === 'video') {
600
+ // OpenAI does not support video input
588
601
  degraded = true;
589
- appendText(utils_1.AUDIO_UNSUPPORTED_TEXT);
602
+ appendText(utils_1.VIDEO_UNSUPPORTED_TEXT);
590
603
  continue;
591
604
  }
592
605
  if (block.type === 'file') {
@@ -626,8 +639,19 @@ class OpenAIProvider {
626
639
  parts.push({ type: textType, text: `<think>${block.reasoning}</think>` });
627
640
  }
628
641
  else if (block.type === 'audio') {
642
+ const audioPart = (0, utils_1.buildOpenAIAudioPart)(block);
643
+ if (audioPart) {
644
+ parts.push(audioPart);
645
+ }
646
+ else {
647
+ degraded = true;
648
+ parts.push({ type: textType, text: utils_1.AUDIO_UNSUPPORTED_TEXT });
649
+ }
650
+ }
651
+ else if (block.type === 'video') {
652
+ // OpenAI Responses API does not support video input
629
653
  degraded = true;
630
- parts.push({ type: textType, text: utils_1.AUDIO_UNSUPPORTED_TEXT });
654
+ parts.push({ type: textType, text: utils_1.VIDEO_UNSUPPORTED_TEXT });
631
655
  }
632
656
  else if (block.type === 'file') {
633
657
  if (block.file_id) {
@@ -60,7 +60,7 @@ export interface UploadFileInput {
60
60
  data: Buffer;
61
61
  mimeType: string;
62
62
  filename?: string;
63
- kind: 'image' | 'file';
63
+ kind: 'image' | 'audio' | 'video' | 'file';
64
64
  }
65
65
  /**
66
66
  * File upload result.
@@ -104,6 +104,35 @@ export interface MultimodalOptions {
104
104
  maxBase64Bytes?: number;
105
105
  /** Allowed MIME types */
106
106
  allowMimeTypes?: string[];
107
+ /** Audio-specific options */
108
+ audio?: {
109
+ /** Allowed audio MIME types */
110
+ allowMimeTypes?: string[];
111
+ /** Maximum audio duration in seconds */
112
+ maxDurationSec?: number;
113
+ /** Custom transcriber callback for providers without native audio support */
114
+ customTranscriber?: (audio: {
115
+ base64?: string;
116
+ url?: string;
117
+ mimeType?: string;
118
+ }) => Promise<string>;
119
+ };
120
+ /** Video-specific options */
121
+ video?: {
122
+ /** Allowed video MIME types */
123
+ allowMimeTypes?: string[];
124
+ /** Maximum video duration in seconds */
125
+ maxDurationSec?: number;
126
+ /** Custom frame extractor callback for providers without native video support */
127
+ customFrameExtractor?: (video: {
128
+ base64?: string;
129
+ url?: string;
130
+ mimeType?: string;
131
+ }) => Promise<Array<{
132
+ base64: string;
133
+ mimeType: string;
134
+ }>>;
135
+ };
107
136
  }
108
137
  /**
109
138
  * Core model configuration.
@@ -185,12 +214,17 @@ export interface ProviderCapabilities {
185
214
  supportsInterleavedThinking: boolean;
186
215
  supportsImages: boolean;
187
216
  supportsAudio: boolean;
217
+ supportsVideo: boolean;
218
+ supportsAudioOutput: boolean;
188
219
  supportsFiles: boolean;
189
220
  supportsTools: boolean;
190
221
  supportsStreaming: boolean;
191
222
  supportsCache: boolean;
192
223
  maxContextTokens: number;
193
224
  maxOutputTokens: number;
225
+ maxAudioDurationSec?: number;
226
+ maxVideoDurationSec?: number;
227
+ maxInlineDataBytes?: number;
194
228
  minCacheableTokens?: number;
195
229
  maxCacheBreakpoints?: number;
196
230
  }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Shared utilities for provider implementations.
3
3
  */
4
- import { ContentBlock, Message, ImageContentBlock, FileContentBlock } from '../../core/types';
4
+ import { ContentBlock, Message, ImageContentBlock, FileContentBlock, AudioContentBlock, VideoContentBlock } from '../../core/types';
5
5
  import { ReasoningTransport } from './types';
6
6
  export declare function resolveProxyUrl(explicit?: string): string | undefined;
7
7
  export declare function getProxyDispatcher(proxyUrl?: string): any | undefined;
@@ -18,6 +18,7 @@ export declare function safeJsonStringify(value: any): string;
18
18
  export declare const FILE_UNSUPPORTED_TEXT = "[file unsupported] This model does not support PDF input. Please extract text or images first.";
19
19
  export declare const IMAGE_UNSUPPORTED_TEXT = "[image unsupported] This model does not support image URLs; please provide base64 data if supported.";
20
20
  export declare const AUDIO_UNSUPPORTED_TEXT = "[audio unsupported] This model does not support audio input; please provide a text transcript instead.";
21
+ export declare const VIDEO_UNSUPPORTED_TEXT = "[video unsupported] This model does not support video input; please provide text description or extracted frames instead.";
21
22
  export declare function concatTextWithReasoning(blocks: ContentBlock[], reasoningTransport?: ReasoningTransport): string;
22
23
  export declare function joinReasoningBlocks(blocks: ContentBlock[]): string;
23
24
  /**
@@ -31,6 +32,23 @@ export declare function splitThinkText(text: string): ContentBlock[];
31
32
  export declare function extractReasoningDetails(message: any): ContentBlock[];
32
33
  export declare function buildGeminiImagePart(block: ImageContentBlock): any | null;
33
34
  export declare function buildGeminiFilePart(block: FileContentBlock): any | null;
35
+ export declare function buildGeminiAudioPart(block: AudioContentBlock): any | null;
36
+ export declare function buildGeminiVideoPart(block: VideoContentBlock): any | null;
37
+ /** Supported OpenAI audio formats */
38
+ export declare const OPENAI_SUPPORTED_AUDIO_FORMATS: readonly ["wav", "mp3"];
39
+ export type OpenAIAudioFormat = (typeof OPENAI_SUPPORTED_AUDIO_FORMATS)[number];
40
+ /**
41
+ * Extract and validate OpenAI audio format from MIME type.
42
+ * OpenAI Chat Completions API only supports wav and mp3.
43
+ * @returns The audio format if supported, null otherwise
44
+ */
45
+ export declare function extractOpenAIAudioFormat(mimeType?: string): OpenAIAudioFormat | null;
46
+ /**
47
+ * Build OpenAI input_audio content part from AudioContentBlock.
48
+ * OpenAI only supports base64 encoded audio (no URLs).
49
+ * @returns The OpenAI input_audio part or null if not supported
50
+ */
51
+ export declare function buildOpenAIAudioPart(block: AudioContentBlock): any | null;
34
52
  export declare function sanitizeGeminiSchema(schema: any): any;
35
53
  export declare function hasAnthropicFileBlocks(messages: Message[]): boolean;
36
54
  export declare function mergeAnthropicBetaHeader(existing: string | undefined, entries: string[]): string | undefined;