@shareai-lab/kode-sdk 2.7.2 → 2.7.3

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.
@@ -14,6 +14,7 @@ export declare class SkillsManager {
14
14
  private skillsDir;
15
15
  private cache;
16
16
  private allowedSkills?;
17
+ private skillsDisabled;
17
18
  constructor(skillsDir?: string, allowedSkills?: string[]);
18
19
  /**
19
20
  * 扫描skills目录(支持热更新)
@@ -30,10 +31,13 @@ export declare class SkillsManager {
30
31
  loadSkillContent(skillName: string): Promise<SkillContent | null>;
31
32
  /**
32
33
  * 递归扫描目录,查找所有SKILL.md
34
+ * 返回格式: { skillMdPath: string, folderName: string }[]
33
35
  */
34
36
  private scanDirectory;
35
37
  /**
36
38
  * 解析SKILL.md,提取元数据
39
+ * @param skillMdPath SKILL.md文件的完整路径
40
+ * @param folderName 技能文件夹名称(作为技能标识符)
37
41
  */
38
42
  private parseSkillMetadata;
39
43
  /**
@@ -51,17 +51,35 @@ const logger_1 = require("../../utils/logger");
51
51
  class SkillsManager {
52
52
  constructor(skillsDir, allowedSkills) {
53
53
  this.cache = new Map();
54
+ this.skillsDisabled = false;
54
55
  // 优先使用传入的路径,其次使用环境变量,最后使用默认路径
55
- // 默认路径:程序当前工作目录下的 skills/
56
+ // 默认路径:程序当前工作目录下的 .skills/
56
57
  const envSkillsDir = process.env.SKILLS_DIR;
57
- const defaultSkillsDir = path.join(process.cwd(), 'skills');
58
+ const defaultSkillsDir = path.join(process.cwd(), '.skills');
58
59
  this.skillsDir = path.resolve(skillsDir ||
59
60
  envSkillsDir ||
60
61
  defaultSkillsDir);
61
62
  // 设置允许加载的 skills 白名单
62
- this.allowedSkills = allowedSkills;
63
+ // 特殊处理:
64
+ // 1. 如果白名单包含 "/*/",则完全禁用技能功能
65
+ // 2. 如果白名单包含"*",则视为未设置白名单(加载所有技能)
66
+ if (allowedSkills && allowedSkills.length === 1 && allowedSkills[0] === '/*/') {
67
+ this.skillsDisabled = true;
68
+ this.allowedSkills = [];
69
+ logger_1.logger.log(`[SkillsManager] Skills disabled (whitelist is "/*/")`);
70
+ }
71
+ else if (allowedSkills && allowedSkills.length === 1 && allowedSkills[0] === '*') {
72
+ this.allowedSkills = undefined;
73
+ logger_1.logger.log(`[SkillsManager] Whitelist contains "*", loading all skills`);
74
+ }
75
+ else {
76
+ this.allowedSkills = allowedSkills;
77
+ }
63
78
  logger_1.logger.log(`[SkillsManager] Initialized with skills directory: ${this.skillsDir}`);
64
- if (this.allowedSkills) {
79
+ if (this.skillsDisabled) {
80
+ logger_1.logger.log(`[SkillsManager] Skills feature is disabled`);
81
+ }
82
+ else if (this.allowedSkills) {
65
83
  logger_1.logger.log(`[SkillsManager] Allowed skills whitelist: ${this.allowedSkills.join(', ')}`);
66
84
  }
67
85
  }
@@ -70,6 +88,11 @@ class SkillsManager {
70
88
  * 每次调用时重新扫描,确保读取最新数据
71
89
  */
72
90
  async scan() {
91
+ // 如果技能功能被禁用,直接返回空数组
92
+ if (this.skillsDisabled) {
93
+ logger_1.logger.log(`[SkillsManager] Skills disabled, skipping scan`);
94
+ return [];
95
+ }
73
96
  // 清空缓存
74
97
  this.cache.clear();
75
98
  try {
@@ -83,7 +106,7 @@ class SkillsManager {
83
106
  const entries = await this.scanDirectory(this.skillsDir);
84
107
  // 提取每个skill的元数据
85
108
  for (const entry of entries) {
86
- const metadata = await this.parseSkillMetadata(entry);
109
+ const metadata = await this.parseSkillMetadata(entry.skillMdPath, entry.folderName);
87
110
  if (metadata) {
88
111
  // 如果设置了白名单,只加载白名单中的 skills
89
112
  if (this.allowedSkills) {
@@ -147,9 +170,10 @@ class SkillsManager {
147
170
  }
148
171
  /**
149
172
  * 递归扫描目录,查找所有SKILL.md
173
+ * 返回格式: { skillMdPath: string, folderName: string }[]
150
174
  */
151
175
  async scanDirectory(dir) {
152
- const skillFiles = [];
176
+ const skills = [];
153
177
  try {
154
178
  const entries = await fs.readdir(dir, { withFileTypes: true });
155
179
  for (const entry of entries) {
@@ -158,12 +182,13 @@ class SkillsManager {
158
182
  // 检查是否有SKILL.md
159
183
  const skillMdPath = path.join(fullPath, 'SKILL.md');
160
184
  if (await this.fileExists(skillMdPath)) {
161
- skillFiles.push(skillMdPath);
185
+ // 返回SKILL.md路径和文件夹名称
186
+ skills.push({ skillMdPath, folderName: entry.name });
162
187
  }
163
188
  else {
164
189
  // 递归扫描子目录
165
190
  const subSkills = await this.scanDirectory(fullPath);
166
- skillFiles.push(...subSkills);
191
+ skills.push(...subSkills);
167
192
  }
168
193
  }
169
194
  }
@@ -171,12 +196,14 @@ class SkillsManager {
171
196
  catch (error) {
172
197
  logger_1.logger.warn(`[SkillsManager] Error reading directory ${dir}:`, error.message);
173
198
  }
174
- return skillFiles;
199
+ return skills;
175
200
  }
176
201
  /**
177
202
  * 解析SKILL.md,提取元数据
203
+ * @param skillMdPath SKILL.md文件的完整路径
204
+ * @param folderName 技能文件夹名称(作为技能标识符)
178
205
  */
179
- async parseSkillMetadata(skillMdPath) {
206
+ async parseSkillMetadata(skillMdPath, folderName) {
180
207
  try {
181
208
  const content = await fs.readFile(skillMdPath, 'utf-8');
182
209
  // 提取YAML frontmatter
@@ -186,14 +213,10 @@ class SkillsManager {
186
213
  return null;
187
214
  }
188
215
  const yaml = match[1];
189
- const nameMatch = yaml.match(/^name:\s*(.+)$/m);
190
216
  const descMatch = yaml.match(/^description:\s*(.+)$/m);
191
- if (!nameMatch) {
192
- logger_1.logger.warn(`[SkillsManager] Invalid SKILL.md (missing name): ${skillMdPath}`);
193
- return null;
194
- }
217
+ // 使用文件夹名称作为技能标识符,而不是从YAML中读取name
195
218
  return {
196
- name: nameMatch[1].trim(),
219
+ name: folderName,
197
220
  description: descMatch ? descMatch[1].trim() : '',
198
221
  path: skillMdPath,
199
222
  baseDir: path.dirname(skillMdPath),
@@ -59,6 +59,8 @@ export interface SkillInfo {
59
59
  path: string;
60
60
  /** 技能根目录 */
61
61
  baseDir: string;
62
+ /** 文件夹名称(技能目录名) */
63
+ folderName: string;
62
64
  /** 创建时间 */
63
65
  createdAt?: string;
64
66
  /** 更新时间 */
@@ -115,6 +117,16 @@ export interface ArchivedSkillInfo {
115
117
  archivedName: string;
116
118
  /** archived目录中的完整路径 */
117
119
  archivedPath: string;
120
+ /** 文件夹名称(归档目录名) */
121
+ folderName: string;
118
122
  /** 归档时间 */
119
123
  archivedAt: string;
124
+ /** 技能名称(从SKILL.md解析) */
125
+ name?: string;
126
+ /** 技能描述(从SKILL.md解析) */
127
+ description?: string;
128
+ /** 许可证(从SKILL.md解析) */
129
+ license?: string;
130
+ /** 其他元数据 */
131
+ metadata?: Record<string, string>;
120
132
  }
@@ -18,7 +18,7 @@ export type ContentBlock = {
18
18
  tool_use_id: string;
19
19
  content: any;
20
20
  is_error?: boolean;
21
- } | ReasoningContentBlock | ImageContentBlock | AudioContentBlock | FileContentBlock;
21
+ } | ReasoningContentBlock | ImageContentBlock | AudioContentBlock | VideoContentBlock | FileContentBlock;
22
22
  export type ReasoningContentBlock = {
23
23
  type: 'reasoning';
24
24
  reasoning: string;
@@ -40,6 +40,14 @@ export type AudioContentBlock = {
40
40
  mime_type?: string;
41
41
  meta?: Record<string, any>;
42
42
  };
43
+ export type VideoContentBlock = {
44
+ type: 'video';
45
+ url?: string;
46
+ file_id?: string;
47
+ base64?: string;
48
+ mime_type?: string;
49
+ meta?: Record<string, any>;
50
+ };
43
51
  export type FileContentBlock = {
44
52
  type: 'file';
45
53
  url?: string;
@@ -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;
@@ -3,7 +3,7 @@
3
3
  * Shared utilities for provider implementations.
4
4
  */
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.AUDIO_UNSUPPORTED_TEXT = exports.IMAGE_UNSUPPORTED_TEXT = exports.FILE_UNSUPPORTED_TEXT = void 0;
6
+ exports.OPENAI_SUPPORTED_AUDIO_FORMATS = exports.VIDEO_UNSUPPORTED_TEXT = exports.AUDIO_UNSUPPORTED_TEXT = exports.IMAGE_UNSUPPORTED_TEXT = exports.FILE_UNSUPPORTED_TEXT = void 0;
7
7
  exports.resolveProxyUrl = resolveProxyUrl;
8
8
  exports.getProxyDispatcher = getProxyDispatcher;
9
9
  exports.withProxy = withProxy;
@@ -23,6 +23,10 @@ exports.splitThinkText = splitThinkText;
23
23
  exports.extractReasoningDetails = extractReasoningDetails;
24
24
  exports.buildGeminiImagePart = buildGeminiImagePart;
25
25
  exports.buildGeminiFilePart = buildGeminiFilePart;
26
+ exports.buildGeminiAudioPart = buildGeminiAudioPart;
27
+ exports.buildGeminiVideoPart = buildGeminiVideoPart;
28
+ exports.extractOpenAIAudioFormat = extractOpenAIAudioFormat;
29
+ exports.buildOpenAIAudioPart = buildOpenAIAudioPart;
26
30
  exports.sanitizeGeminiSchema = sanitizeGeminiSchema;
27
31
  exports.hasAnthropicFileBlocks = hasAnthropicFileBlocks;
28
32
  exports.mergeAnthropicBetaHeader = mergeAnthropicBetaHeader;
@@ -150,6 +154,7 @@ function safeJsonStringify(value) {
150
154
  exports.FILE_UNSUPPORTED_TEXT = '[file unsupported] This model does not support PDF input. Please extract text or images first.';
151
155
  exports.IMAGE_UNSUPPORTED_TEXT = '[image unsupported] This model does not support image URLs; please provide base64 data if supported.';
152
156
  exports.AUDIO_UNSUPPORTED_TEXT = '[audio unsupported] This model does not support audio input; please provide a text transcript instead.';
157
+ exports.VIDEO_UNSUPPORTED_TEXT = '[video unsupported] This model does not support video input; please provide text description or extracted frames instead.';
153
158
  // =============================================================================
154
159
  // Reasoning/Thinking Utilities
155
160
  // =============================================================================
@@ -270,6 +275,81 @@ function buildGeminiFilePart(block) {
270
275
  }
271
276
  return null;
272
277
  }
278
+ function buildGeminiAudioPart(block) {
279
+ const mimeType = block.mime_type || 'audio/wav';
280
+ if (block.file_id) {
281
+ return { file_data: { mime_type: mimeType, file_uri: block.file_id } };
282
+ }
283
+ if (block.url) {
284
+ if (block.url.startsWith('gs://')) {
285
+ return { file_data: { mime_type: mimeType, file_uri: block.url } };
286
+ }
287
+ // Gemini supports https URLs for audio via file_data
288
+ return { file_data: { mime_type: mimeType, file_uri: block.url } };
289
+ }
290
+ if (block.base64) {
291
+ return { inline_data: { mime_type: mimeType, data: block.base64 } };
292
+ }
293
+ return null;
294
+ }
295
+ function buildGeminiVideoPart(block) {
296
+ const mimeType = block.mime_type || 'video/mp4';
297
+ if (block.file_id) {
298
+ return { file_data: { mime_type: mimeType, file_uri: block.file_id } };
299
+ }
300
+ if (block.url) {
301
+ if (block.url.startsWith('gs://')) {
302
+ return { file_data: { mime_type: mimeType, file_uri: block.url } };
303
+ }
304
+ // Gemini supports https URLs for video via file_data
305
+ return { file_data: { mime_type: mimeType, file_uri: block.url } };
306
+ }
307
+ if (block.base64) {
308
+ return { inline_data: { mime_type: mimeType, data: block.base64 } };
309
+ }
310
+ return null;
311
+ }
312
+ // =============================================================================
313
+ // OpenAI Audio Helpers
314
+ // =============================================================================
315
+ /** Supported OpenAI audio formats */
316
+ exports.OPENAI_SUPPORTED_AUDIO_FORMATS = ['wav', 'mp3'];
317
+ /**
318
+ * Extract and validate OpenAI audio format from MIME type.
319
+ * OpenAI Chat Completions API only supports wav and mp3.
320
+ * @returns The audio format if supported, null otherwise
321
+ */
322
+ function extractOpenAIAudioFormat(mimeType) {
323
+ if (!mimeType)
324
+ return null;
325
+ const lower = mimeType.toLowerCase();
326
+ if (lower === 'audio/wav' || lower === 'audio/x-wav' || lower === 'audio/wave') {
327
+ return 'wav';
328
+ }
329
+ if (lower === 'audio/mpeg' || lower === 'audio/mp3') {
330
+ return 'mp3';
331
+ }
332
+ return null;
333
+ }
334
+ /**
335
+ * Build OpenAI input_audio content part from AudioContentBlock.
336
+ * OpenAI only supports base64 encoded audio (no URLs).
337
+ * @returns The OpenAI input_audio part or null if not supported
338
+ */
339
+ function buildOpenAIAudioPart(block) {
340
+ const format = extractOpenAIAudioFormat(block.mime_type);
341
+ if (!format)
342
+ return null;
343
+ if (!block.base64)
344
+ return null;
345
+ return {
346
+ type: 'input_audio',
347
+ input_audio: {
348
+ data: block.base64,
349
+ format,
350
+ },
351
+ };
352
+ }
273
353
  function sanitizeGeminiSchema(schema) {
274
354
  if (schema === null || schema === undefined)
275
355
  return schema;
@@ -582,7 +582,8 @@ class JSONStore {
582
582
  const fs = require('fs').promises;
583
583
  try {
584
584
  await fs.access(this.getAgentDir(agentId));
585
- return true;
585
+ const info = await this.loadInfo(agentId);
586
+ return !!(info && info.metadata);
586
587
  }
587
588
  catch {
588
589
  return false;
@@ -51,11 +51,11 @@ function createSkillsTool(skillsManager) {
51
51
  const { action, skill_name } = args;
52
52
  // 注释掉 list 操作的代码
53
53
  // if (action === 'list') {
54
- // // 列出所有skills
54
+ // // 列出所有skills(使用文件夹名称作为标识符)
55
55
  // const skills = await skillsManager.getSkillsMetadata();
56
56
  //
57
57
  // const skillsList = skills.map(s => ({
58
- // name: s.name,
58
+ // name: s.name, // 文件夹名称
59
59
  // description: s.description,
60
60
  // }));
61
61
  //
@@ -87,7 +87,7 @@ export declare const patterns: {
87
87
  /**
88
88
  * 字符串数组
89
89
  */
90
- stringArray: (description?: string) => z.ZodArray<z.ZodString, "many">;
90
+ stringArray: (description?: string) => z.ZodArray<z.ZodString>;
91
91
  /**
92
92
  * 可选字符串
93
93
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shareai-lab/kode-sdk",
3
- "version": "2.7.2",
3
+ "version": "2.7.3",
4
4
  "description": "Event-driven, long-running AI Agent development framework with enterprise-grade persistence and context management",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -50,8 +50,8 @@
50
50
  "fast-glob": "^3.3.2",
51
51
  "pg": "^8.17.2",
52
52
  "undici": "^7.18.2",
53
- "zod": "~3.23.8",
54
- "zod-to-json-schema": "~3.23.0"
53
+ "zod": "^4.3.5",
54
+ "zod-to-json-schema": "^3.24.6"
55
55
  },
56
56
  "devDependencies": {
57
57
  "@shareai-lab/kode-sdk": "file:.",