{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "agent-ai-sdk", "title": "Agent AI SDK Direct Adapter", "description": "In-memory OpenAI-compatible connection editor, bounded model discovery and files, AI SDK ToolLoopAgent DirectChatTransport, and useChat lifecycle", "dependencies": [ "@ai-sdk/openai-compatible@3.0.12", "@ai-sdk/react@4.0.34", "ai@7.0.31", "daisyui", "lucide-react", "zod@4.4.3" ], "registryDependencies": ["https://ui-components.wener.me/r/agent-chat.json"], "files": [ { "path": "registry/default/blocks/agent-ai-sdk/connection-config.ts", "content": "import { z } from 'zod';\n\nexport type OpenAICompatibleConnectionConfig = {\n\tapiKey?: string;\n\tbaseUrl: string;\n\theaders?: Record;\n\tmodel: string;\n};\n\nexport type OpenAICompatibleConnectionDraft = OpenAICompatibleConnectionConfig;\n\nexport type ConnectionValidationIssue = {\n\tfield: 'apiKey' | 'baseUrl' | 'headers' | 'model';\n\tmessage: string;\n};\n\nexport type ConnectionValidationResult =\n\t| { success: true; value: OpenAICompatibleConnectionConfig }\n\t| { issues: ConnectionValidationIssue[]; success: false };\n\nconst baseUrlSchema = z\n\t.string()\n\t.trim()\n\t.min(1, '请输入 Base URL。')\n\t.max(2048, 'Base URL 过长。')\n\t.refine(isAllowedBaseUrl, 'Base URL 必须是有效的 HTTP 或 HTTPS 地址,且不能包含凭据、查询参数或片段。')\n\t.transform(normalizeOpenAICompatibleBaseUrl);\n\nconst connectionSchema = z\n\t.object({\n\t\tapiKey: z\n\t\t\t.string()\n\t\t\t.max(16 * 1024, 'API Key 过长。')\n\t\t\t.optional(),\n\t\tbaseUrl: baseUrlSchema,\n\t\theaders: z\n\t\t\t.record(z.string().min(1).max(128), z.string().max(8 * 1024))\n\t\t\t.refine((headers) => Object.keys(headers).length <= 64, '请求头不能超过 64 项。')\n\t\t\t.refine(\n\t\t\t\t(headers) => Object.entries(headers).every(([name, value]) => !/[\\r\\n]/u.test(name) && !/[\\r\\n]/u.test(value)),\n\t\t\t\t'请求头不能包含换行符。',\n\t\t\t)\n\t\t\t.optional(),\n\t\tmodel: z.string().trim().min(1, '请输入模型。').max(256, '模型名称过长。'),\n\t})\n\t.strict();\n\nexport function normalizeOpenAICompatibleBaseUrl(value: string): string {\n\treturn value.replace(/\\/+$/u, '');\n}\n\nexport function validateOpenAICompatibleBaseUrl(\n\tvalue: string,\n): { success: true; value: string } | { message: string; success: false } {\n\tconst result = baseUrlSchema.safeParse(value);\n\treturn result.success\n\t\t? { success: true, value: result.data }\n\t\t: { success: false, message: result.error.issues[0]?.message ?? 'Base URL 无效。' };\n}\n\nexport function validateOpenAICompatibleConnection(input: unknown): ConnectionValidationResult {\n\tconst result = connectionSchema.safeParse(input);\n\tif (result.success) return { success: true, value: result.data };\n\treturn {\n\t\tsuccess: false,\n\t\tissues: result.error.issues.slice(0, 8).map((issue) => ({\n\t\t\tfield: toConnectionField(issue.path[0]),\n\t\t\tmessage: issue.message,\n\t\t})),\n\t};\n}\n\nexport function requireOpenAICompatibleConnection(input: unknown): OpenAICompatibleConnectionConfig {\n\tconst result = validateOpenAICompatibleConnection(input);\n\tif (result.success) return result.value;\n\tthrow new Error(result.issues[0]?.message ?? '连接配置无效。');\n}\n\nexport function sameOpenAICompatibleConnection(\n\tleft: OpenAICompatibleConnectionConfig,\n\tright: OpenAICompatibleConnectionConfig,\n): boolean {\n\tif (left.baseUrl !== right.baseUrl || left.apiKey !== right.apiKey || left.model !== right.model) return false;\n\tconst leftHeaders = left.headers ?? {};\n\tconst rightHeaders = right.headers ?? {};\n\tconst leftKeys = Object.keys(leftHeaders);\n\tconst rightKeys = Object.keys(rightHeaders);\n\treturn leftKeys.length === rightKeys.length && leftKeys.every((key) => rightHeaders[key] === leftHeaders[key]);\n}\n\nfunction isAllowedBaseUrl(value: string): boolean {\n\ttry {\n\t\tconst url = new URL(value);\n\t\treturn (\n\t\t\t(url.protocol === 'http:' || url.protocol === 'https:') &&\n\t\t\turl.hostname.length > 0 &&\n\t\t\turl.username.length === 0 &&\n\t\t\turl.password.length === 0 &&\n\t\t\turl.search.length === 0 &&\n\t\t\turl.hash.length === 0\n\t\t);\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nfunction toConnectionField(value: PropertyKey | undefined): ConnectionValidationIssue['field'] {\n\tif (value === 'apiKey' || value === 'headers' || value === 'model') return value;\n\treturn 'baseUrl';\n}\n", "type": "registry:lib", "target": "@components/blocks/agent-ai-sdk/connection-config.ts" }, { "path": "registry/default/blocks/agent-ai-sdk/safe-error.ts", "content": "export const AGENT_SAFE_ERROR_MAX_LENGTH = 320;\n\nexport function toSafeAgentError(error: unknown, secrets: readonly string[] = []): Error {\n\tif (isAbortError(error)) return new DOMException('操作已取消。', 'AbortError');\n\tlet message = error instanceof Error ? error.message : typeof error === 'string' ? error : '请求失败。';\n\tfor (const secret of secrets) {\n\t\tif (secret.length > 0) message = message.split(secret).join('[已隐藏]');\n\t}\n\tmessage = message\n\t\t.replace(/\\bBearer\\s+[^\\s,;]+/giu, 'Bearer [已隐藏]')\n\t\t.replace(/\\b(authorization|api[-_ ]?key)\\s*[:=]\\s*[^\\s,;]+/giu, '$1=[已隐藏]')\n\t\t.replace(/https?:\\/\\/[^\\s)\\]}>'\"]+/giu, '[服务地址]')\n\t\t.replace(/[\\r\\n\\t]+/gu, ' ')\n\t\t.trim();\n\tif (message.length === 0) message = '请求失败。';\n\tif (message.length > AGENT_SAFE_ERROR_MAX_LENGTH) message = `${message.slice(0, AGENT_SAFE_ERROR_MAX_LENGTH - 1)}…`;\n\treturn new Error(message);\n}\n\nexport function isAbortError(error: unknown): boolean {\n\treturn error instanceof DOMException\n\t\t? error.name === 'AbortError'\n\t\t: error instanceof Error && error.name === 'AbortError';\n}\n", "type": "registry:lib", "target": "@components/blocks/agent-ai-sdk/safe-error.ts" }, { "path": "registry/default/blocks/agent-ai-sdk/model-list.ts", "content": "import { z } from 'zod';\nimport { validateOpenAICompatibleBaseUrl } from './connection-config';\n\nexport const OPENAI_MODEL_LIST_DEFAULT_TIMEOUT_MS = 10_000;\nexport const OPENAI_MODEL_LIST_DEFAULT_MAX_BYTES = 1024 * 1024;\nexport const OPENAI_MODEL_LIST_MAX_MODELS = 1000;\n\nexport type OpenAICompatibleModel = {\n\tcreated?: number;\n\tid: string;\n\townedBy?: string;\n};\n\nexport type OpenAICompatibleModelListConfig = {\n\tapiKey?: string;\n\tbaseUrl: string;\n\theaders?: Record;\n};\n\nexport type OpenAICompatibleModelListOptions = {\n\tfetch?: typeof globalThis.fetch;\n\tmaxResponseBytes?: number;\n\tsignal?: AbortSignal;\n\ttimeoutMs?: number;\n};\n\nexport class OpenAICompatibleModelListError extends Error {\n\treadonly code:\n\t\t| 'aborted'\n\t\t| 'connection'\n\t\t| 'invalid-config'\n\t\t| 'invalid-response'\n\t\t| 'response-too-large'\n\t\t| 'status'\n\t\t| 'timeout';\n\n\tconstructor(code: OpenAICompatibleModelListError['code'], message: string) {\n\t\tsuper(message);\n\t\tthis.name = 'OpenAICompatibleModelListError';\n\t\tthis.code = code;\n\t}\n}\n\nconst modelSchema = z\n\t.object({\n\t\tcreated: z.number().int().nonnegative().optional(),\n\t\tid: z.string().min(1).max(256),\n\t\tobject: z.literal('model').optional(),\n\t\towned_by: z.string().max(256).optional(),\n\t})\n\t.passthrough();\nconst modelListSchema = z\n\t.object({ object: z.literal('list'), data: z.array(modelSchema).max(OPENAI_MODEL_LIST_MAX_MODELS) })\n\t.passthrough();\n\nexport async function listOpenAICompatibleModels(\n\tconfig: OpenAICompatibleModelListConfig,\n\toptions: OpenAICompatibleModelListOptions = {},\n): Promise {\n\tconst baseUrl = validateOpenAICompatibleBaseUrl(config.baseUrl);\n\tif (!baseUrl.success) throw new OpenAICompatibleModelListError('invalid-config', baseUrl.message);\n\tif (options.signal?.aborted) {\n\t\tthrow new OpenAICompatibleModelListError('aborted', '模型列表请求已取消。');\n\t}\n\tconst timeoutMs = resolveBound(options.timeoutMs, OPENAI_MODEL_LIST_DEFAULT_TIMEOUT_MS, 60_000);\n\tconst maxBytes = resolveBound(options.maxResponseBytes, OPENAI_MODEL_LIST_DEFAULT_MAX_BYTES, 4 * 1024 * 1024);\n\tconst controller = new AbortController();\n\tlet timedOut = false;\n\tconst abortFromCaller = () => controller.abort(options.signal?.reason);\n\toptions.signal?.addEventListener('abort', abortFromCaller, { once: true });\n\tconst timer = setTimeout(() => {\n\t\ttimedOut = true;\n\t\tcontroller.abort();\n\t}, timeoutMs);\n\tconst headers = new Headers(config.headers);\n\theaders.set('Accept', 'application/json');\n\theaders.delete('Authorization');\n\tif (config.apiKey?.trim()) headers.set('Authorization', `Bearer ${config.apiKey}`);\n\ttry {\n\t\tconst response = await (options.fetch ?? globalThis.fetch)(`${baseUrl.value}/models`, {\n\t\t\theaders,\n\t\t\tmethod: 'GET',\n\t\t\tsignal: controller.signal,\n\t\t});\n\t\tif (!response.ok) {\n\t\t\tthrow new OpenAICompatibleModelListError('status', `模型列表请求失败(HTTP ${response.status})。`);\n\t\t}\n\t\tconst contentType = response.headers.get('content-type');\n\t\tif (contentType && !/\\bapplication\\/(?:[\\w.+-]+\\+)?json\\b/iu.test(contentType)) {\n\t\t\tthrow new OpenAICompatibleModelListError('invalid-response', '模型列表响应不是 JSON。');\n\t\t}\n\t\tconst text = await readBoundedResponseText(response, maxBytes);\n\t\tlet parsed: unknown;\n\t\ttry {\n\t\t\tparsed = JSON.parse(text);\n\t\t} catch {\n\t\t\tthrow new OpenAICompatibleModelListError('invalid-response', '模型列表响应不是有效 JSON。');\n\t\t}\n\t\tconst validated = modelListSchema.safeParse(parsed);\n\t\tif (!validated.success) {\n\t\t\tthrow new OpenAICompatibleModelListError('invalid-response', '模型列表响应不符合 OpenAI models list 格式。');\n\t\t}\n\t\tconst seen = new Set();\n\t\treturn validated.data.data.flatMap((model) => {\n\t\t\tif (seen.has(model.id)) return [];\n\t\t\tseen.add(model.id);\n\t\t\treturn [{ id: model.id, created: model.created, ownedBy: model.owned_by }];\n\t\t});\n\t} catch (error) {\n\t\tif (error instanceof OpenAICompatibleModelListError) throw error;\n\t\tif (controller.signal.aborted) {\n\t\t\tthrow new OpenAICompatibleModelListError(\n\t\t\t\ttimedOut ? 'timeout' : 'aborted',\n\t\t\t\ttimedOut ? '模型列表请求超时。' : '模型列表请求已取消。',\n\t\t\t);\n\t\t}\n\t\tthrow new OpenAICompatibleModelListError('connection', '无法连接到模型服务。');\n\t} finally {\n\t\tclearTimeout(timer);\n\t\toptions.signal?.removeEventListener('abort', abortFromCaller);\n\t}\n}\n\nasync function readBoundedResponseText(response: Response, maxBytes: number): Promise {\n\tconst contentLength = Number(response.headers.get('content-length'));\n\tif (Number.isFinite(contentLength) && contentLength > maxBytes) {\n\t\tthrow new OpenAICompatibleModelListError('response-too-large', '模型列表响应超过大小限制。');\n\t}\n\tif (!response.body) return '';\n\tconst reader = response.body.getReader();\n\tconst chunks: Uint8Array[] = [];\n\tlet total = 0;\n\ttry {\n\t\twhile (true) {\n\t\t\tconst { done, value } = await reader.read();\n\t\t\tif (done) break;\n\t\t\ttotal += value.byteLength;\n\t\t\tif (total > maxBytes) {\n\t\t\t\tawait reader.cancel();\n\t\t\t\tthrow new OpenAICompatibleModelListError('response-too-large', '模型列表响应超过大小限制。');\n\t\t\t}\n\t\t\tchunks.push(value);\n\t\t}\n\t} finally {\n\t\treader.releaseLock();\n\t}\n\tconst bytes = new Uint8Array(total);\n\tlet offset = 0;\n\tfor (const chunk of chunks) {\n\t\tbytes.set(chunk, offset);\n\t\toffset += chunk.byteLength;\n\t}\n\ttry {\n\t\treturn new TextDecoder('utf-8', { fatal: true }).decode(bytes);\n\t} catch {\n\t\tthrow new OpenAICompatibleModelListError('invalid-response', '模型列表响应不是有效 UTF-8。');\n\t}\n}\n\nfunction resolveBound(value: number | undefined, fallback: number, maximum: number): number {\n\treturn Number.isFinite(value) && value !== undefined && value > 0 ? Math.min(Math.floor(value), maximum) : fallback;\n}\n", "type": "registry:lib", "target": "@components/blocks/agent-ai-sdk/model-list.ts" }, { "path": "registry/default/blocks/agent-ai-sdk/file-parts.ts", "content": "import type { FileUIPart } from 'ai';\nimport { isAbortError } from './safe-error';\n\nexport const AGENT_RUNTIME_DEFAULT_MAX_FILES = 5;\nexport const AGENT_RUNTIME_DEFAULT_MAX_FILE_SIZE = 10 * 1024 * 1024;\nexport const AGENT_RUNTIME_DEFAULT_MAX_TOTAL_SIZE = 20 * 1024 * 1024;\n\nexport type AgentRuntimeFileLimits = {\n\tmaxFileSize?: number;\n\tmaxFiles?: number;\n\tmaxTotalSize?: number;\n};\n\nexport type AgentRuntimeResolvedFileLimits = {\n\tmaxFileSize: number;\n\tmaxFiles: number;\n\tmaxTotalSize: number;\n};\n\nexport class AgentRuntimeFileError extends Error {\n\treadonly code: 'aborted' | 'file-count' | 'file-read' | 'file-size' | 'total-size' | 'unsupported-type';\n\treadonly file?: File;\n\n\tconstructor(code: AgentRuntimeFileError['code'], message: string, file?: File) {\n\t\tsuper(message);\n\t\tthis.name = 'AgentRuntimeFileError';\n\t\tthis.code = code;\n\t\tthis.file = file;\n\t}\n}\n\nexport function resolveAgentRuntimeFileLimits(limits: AgentRuntimeFileLimits = {}): AgentRuntimeResolvedFileLimits {\n\treturn {\n\t\tmaxFiles: resolveLimit(limits.maxFiles, AGENT_RUNTIME_DEFAULT_MAX_FILES, 20),\n\t\tmaxFileSize: resolveLimit(limits.maxFileSize, AGENT_RUNTIME_DEFAULT_MAX_FILE_SIZE, 64 * 1024 * 1024),\n\t\tmaxTotalSize: resolveLimit(limits.maxTotalSize, AGENT_RUNTIME_DEFAULT_MAX_TOTAL_SIZE, 128 * 1024 * 1024),\n\t};\n}\n\nexport async function convertAgentFilesToUIParts(\n\tfiles: readonly File[],\n\tlimits: AgentRuntimeFileLimits = {},\n\tsignal?: AbortSignal,\n): Promise {\n\tconst resolved = resolveAgentRuntimeFileLimits(limits);\n\tvalidateFiles(files, resolved);\n\tif (signal?.aborted) throw abortedError();\n\tconst parts: FileUIPart[] = [];\n\tfor (const file of files) {\n\t\tif (signal?.aborted) throw abortedError();\n\t\tparts.push({\n\t\t\ttype: 'file',\n\t\t\tfilename: file.name || undefined,\n\t\t\tmediaType: resolveMediaType(file),\n\t\t\turl: await readFileAsDataUrl(file, signal),\n\t\t});\n\t}\n\treturn parts;\n}\n\nfunction validateFiles(files: readonly File[], limits: AgentRuntimeResolvedFileLimits) {\n\tif (files.length > limits.maxFiles) {\n\t\tthrow new AgentRuntimeFileError('file-count', `最多发送 ${limits.maxFiles} 个附件。`);\n\t}\n\tlet total = 0;\n\tfor (const file of files) {\n\t\tif (!Number.isFinite(file.size) || file.size < 0 || file.size > limits.maxFileSize) {\n\t\t\tthrow new AgentRuntimeFileError('file-size', `${file.name || '未命名文件'} 超过单个附件大小限制。`, file);\n\t\t}\n\t\tconst mediaType = resolveMediaType(file);\n\t\tconst topLevel = mediaType.split('/', 1)[0];\n\t\tif (topLevel !== 'application' && topLevel !== 'audio' && topLevel !== 'image' && topLevel !== 'text') {\n\t\t\tthrow new AgentRuntimeFileError('unsupported-type', `${file.name || '未命名文件'} 的类型不受支持。`, file);\n\t\t}\n\t\ttotal += file.size;\n\t\tif (total > limits.maxTotalSize) {\n\t\t\tthrow new AgentRuntimeFileError('total-size', '附件总大小超过限制。');\n\t\t}\n\t}\n}\n\nfunction resolveMediaType(file: File): string {\n\treturn file.type.trim().toLowerCase() || 'application/octet-stream';\n}\n\nfunction readFileAsDataUrl(file: File, signal?: AbortSignal): Promise {\n\treturn new Promise((resolve, reject) => {\n\t\tconst reader = new FileReader();\n\t\tconst abort = () => reader.abort();\n\t\tconst cleanup = () => signal?.removeEventListener('abort', abort);\n\t\tsignal?.addEventListener('abort', abort, { once: true });\n\t\treader.onload = () => {\n\t\t\tcleanup();\n\t\t\tif (typeof reader.result === 'string') resolve(reader.result);\n\t\t\telse reject(new AgentRuntimeFileError('file-read', `无法读取 ${file.name || '附件'}。`, file));\n\t\t};\n\t\treader.onerror = () => {\n\t\t\tcleanup();\n\t\t\treject(new AgentRuntimeFileError('file-read', `无法读取 ${file.name || '附件'}。`, file));\n\t\t};\n\t\treader.onabort = () => {\n\t\t\tcleanup();\n\t\t\treject(abortedError());\n\t\t};\n\t\ttry {\n\t\t\treader.readAsDataURL(file);\n\t\t} catch (error) {\n\t\t\tcleanup();\n\t\t\treject(\n\t\t\t\tisAbortError(error)\n\t\t\t\t\t? abortedError()\n\t\t\t\t\t: new AgentRuntimeFileError('file-read', `无法读取 ${file.name || '附件'}。`, file),\n\t\t\t);\n\t\t}\n\t});\n}\n\nfunction abortedError() {\n\treturn new AgentRuntimeFileError('aborted', '附件读取已取消。');\n}\n\nfunction resolveLimit(value: number | undefined, fallback: number, maximum: number) {\n\treturn Number.isFinite(value) && value !== undefined && value > 0 ? Math.min(Math.floor(value), maximum) : fallback;\n}\n", "type": "registry:lib", "target": "@components/blocks/agent-ai-sdk/file-parts.ts" }, { "path": "registry/default/blocks/agent-ai-sdk/direct-transport-limits.ts", "content": "export type OpenAICompatibleDirectTransportTimeouts = {\n\tchunkMs?: number;\n\tstepMs?: number;\n\ttoolMs?: number;\n\ttotalMs?: number;\n};\n\nexport type OpenAICompatibleDirectTransportLimits = {\n\tmaxOutputTokens?: number;\n\tmaxResponseBytes?: number;\n\tmaxResponseChunks?: number;\n\tmaxRetries?: number;\n\tmaxStreamChunks?: number;\n\tmaxStreamProjectionBytes?: number;\n\ttimeout?: OpenAICompatibleDirectTransportTimeouts;\n};\n\nexport type ResolvedOpenAICompatibleDirectTransportLimits = {\n\tmaxOutputTokens: number;\n\tmaxResponseBytes: number;\n\tmaxResponseChunks: number;\n\tmaxRetries: number;\n\tmaxStreamChunks: number;\n\tmaxStreamProjectionBytes: number;\n\ttimeout: Required;\n};\n\nexport const OPENAI_COMPATIBLE_DIRECT_TRANSPORT_CEILINGS: ResolvedOpenAICompatibleDirectTransportLimits = {\n\tmaxOutputTokens: 8_192,\n\tmaxResponseBytes: 8 * 1024 * 1024,\n\tmaxResponseChunks: 16_384,\n\tmaxRetries: 2,\n\tmaxStreamChunks: 16_384,\n\tmaxStreamProjectionBytes: 8 * 1024 * 1024,\n\ttimeout: {\n\t\tchunkMs: 20_000,\n\t\tstepMs: 60_000,\n\t\ttoolMs: 30_000,\n\t\ttotalMs: 120_000,\n\t},\n};\n\nexport function resolveOpenAICompatibleDirectTransportLimits(\n\tlimits: OpenAICompatibleDirectTransportLimits = {},\n): ResolvedOpenAICompatibleDirectTransportLimits {\n\treturn {\n\t\tmaxOutputTokens: boundedInteger(\n\t\t\tlimits.maxOutputTokens,\n\t\t\tOPENAI_COMPATIBLE_DIRECT_TRANSPORT_CEILINGS.maxOutputTokens,\n\t\t\t1,\n\t\t\t'maxOutputTokens',\n\t\t),\n\t\tmaxResponseBytes: boundedInteger(\n\t\t\tlimits.maxResponseBytes,\n\t\t\tOPENAI_COMPATIBLE_DIRECT_TRANSPORT_CEILINGS.maxResponseBytes,\n\t\t\t1,\n\t\t\t'maxResponseBytes',\n\t\t),\n\t\tmaxResponseChunks: boundedInteger(\n\t\t\tlimits.maxResponseChunks,\n\t\t\tOPENAI_COMPATIBLE_DIRECT_TRANSPORT_CEILINGS.maxResponseChunks,\n\t\t\t1,\n\t\t\t'maxResponseChunks',\n\t\t),\n\t\tmaxRetries: boundedInteger(\n\t\t\tlimits.maxRetries,\n\t\t\tOPENAI_COMPATIBLE_DIRECT_TRANSPORT_CEILINGS.maxRetries,\n\t\t\t0,\n\t\t\t'maxRetries',\n\t\t),\n\t\tmaxStreamChunks: boundedInteger(\n\t\t\tlimits.maxStreamChunks,\n\t\t\tOPENAI_COMPATIBLE_DIRECT_TRANSPORT_CEILINGS.maxStreamChunks,\n\t\t\t1,\n\t\t\t'maxStreamChunks',\n\t\t),\n\t\tmaxStreamProjectionBytes: boundedInteger(\n\t\t\tlimits.maxStreamProjectionBytes,\n\t\t\tOPENAI_COMPATIBLE_DIRECT_TRANSPORT_CEILINGS.maxStreamProjectionBytes,\n\t\t\t1,\n\t\t\t'maxStreamProjectionBytes',\n\t\t),\n\t\ttimeout: {\n\t\t\tchunkMs: timeoutValue(limits.timeout?.chunkMs, 'chunkMs'),\n\t\t\tstepMs: timeoutValue(limits.timeout?.stepMs, 'stepMs'),\n\t\t\ttoolMs: timeoutValue(limits.timeout?.toolMs, 'toolMs'),\n\t\t\ttotalMs: timeoutValue(limits.timeout?.totalMs, 'totalMs'),\n\t\t},\n\t};\n}\n\nexport function wrapBoundedResponse(\n\tresponse: Response,\n\tlimits: Pick,\n): Response {\n\tif (!response.body) return response;\n\tconst reader = response.body.getReader();\n\tlet bytes = 0;\n\tlet chunks = 0;\n\tconst body = new ReadableStream({\n\t\tasync pull(controller) {\n\t\t\ttry {\n\t\t\t\tconst result = await reader.read();\n\t\t\t\tif (result.done) {\n\t\t\t\t\tcontroller.close();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tchunks += 1;\n\t\t\t\tbytes += result.value.byteLength;\n\t\t\t\tif (chunks > limits.maxResponseChunks || bytes > limits.maxResponseBytes) {\n\t\t\t\t\tawait reader.cancel('response limit exceeded').catch(() => undefined);\n\t\t\t\t\tcontroller.error(new Error('模型响应超过客户端安全限制。'));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tcontroller.enqueue(result.value);\n\t\t\t} catch (error) {\n\t\t\t\tcontroller.error(error);\n\t\t\t}\n\t\t},\n\t\tcancel(reason) {\n\t\t\treturn reader.cancel(reason);\n\t\t},\n\t});\n\treturn new Response(body, {\n\t\theaders: response.headers,\n\t\tstatus: response.status,\n\t\tstatusText: response.statusText,\n\t});\n}\n\nfunction timeoutValue(value: number | undefined, name: keyof OpenAICompatibleDirectTransportTimeouts): number {\n\treturn boundedInteger(value, OPENAI_COMPATIBLE_DIRECT_TRANSPORT_CEILINGS.timeout[name]!, 1, `timeout.${name}`);\n}\n\nfunction boundedInteger(value: number | undefined, ceiling: number, minimum: number, name: string): number {\n\tif (value === undefined) return ceiling;\n\tif (!Number.isInteger(value) || value < minimum || value > ceiling) {\n\t\tthrow new Error(`${name} 必须是 ${minimum} 到 ${ceiling} 之间的整数。`);\n\t}\n\treturn value;\n}\n", "type": "registry:lib", "target": "@components/blocks/agent-ai-sdk/direct-transport-limits.ts" }, { "path": "registry/default/blocks/agent-ai-sdk/direct-transport.ts", "content": "import { createOpenAICompatible } from '@ai-sdk/openai-compatible';\nimport {\n\ttype ChatTransport,\n\tDirectChatTransport,\n\ttype Instructions,\n\tisStepCount,\n\tToolLoopAgent,\n\ttype ToolLoopAgentSettings,\n\ttype ToolSet,\n\ttype UIMessage,\n\ttype UIMessageChunk,\n} from 'ai';\nimport { measureAgentRuntimeValue } from '../agent-chat/runtime-message-validation';\nimport type { OpenAICompatibleConnectionConfig } from './connection-config';\nimport { requireOpenAICompatibleConnection } from './connection-config';\nimport {\n\ttype OpenAICompatibleDirectTransportLimits,\n\ttype ResolvedOpenAICompatibleDirectTransportLimits,\n\tresolveOpenAICompatibleDirectTransportLimits,\n\twrapBoundedResponse,\n} from './direct-transport-limits';\nimport { toSafeAgentError } from './safe-error';\n\nexport type OpenAICompatibleDirectTransportOptions = {\n\tconnection: OpenAICompatibleConnectionConfig;\n\tfetch?: typeof globalThis.fetch;\n\tinstructions?: Instructions;\n\tlimits?: OpenAICompatibleDirectTransportLimits;\n\tmaxSteps?: number;\n\ttools?: ToolSet;\n};\n\nexport function createOpenAICompatibleDirectTransport({\n\tconnection: input,\n\tfetch,\n\tinstructions,\n\tlimits: inputLimits,\n\tmaxSteps = 8,\n\ttools,\n}: OpenAICompatibleDirectTransportOptions): ChatTransport {\n\tconst connection = requireOpenAICompatibleConnection(input);\n\tconst limits = resolveOpenAICompatibleDirectTransportLimits(inputLimits);\n\tif (!Number.isInteger(maxSteps) || maxSteps < 1 || maxSteps > 20) {\n\t\tthrow new Error('最大步骤数必须是 1 到 20 之间的整数。');\n\t}\n\tconst secrets = [connection.apiKey ?? '', ...Object.values(connection.headers ?? {})].filter(Boolean);\n\tconst baseFetch = fetch ?? globalThis.fetch;\n\tconst safeFetch: typeof globalThis.fetch = async (request, init) => {\n\t\ttry {\n\t\t\treturn wrapBoundedResponse(await baseFetch(request, init), limits);\n\t\t} catch (error) {\n\t\t\tthrow toSafeAgentError(error, secrets);\n\t\t}\n\t};\n\tconst provider = createOpenAICompatible({\n\t\tapiKey: connection.apiKey?.trim() ? connection.apiKey : undefined,\n\t\tbaseURL: connection.baseUrl,\n\t\tfetch: safeFetch,\n\t\theaders: withoutAuthorizationHeader(connection.headers),\n\t\tname: 'compatible-direct',\n\t});\n\tconst agent = new ToolLoopAgent({\n\t\tinstructions,\n\t\tmaxOutputTokens: limits.maxOutputTokens,\n\t\tmaxRetries: limits.maxRetries,\n\t\tmodel: provider(connection.model),\n\t\tstopWhen: isStepCount(maxSteps),\n\t\ttimeout: limits.timeout,\n\t\ttools: tools ?? {},\n\t} as ToolLoopAgentSettings);\n\tconst direct = new DirectChatTransport({\n\t\tagent,\n\t\tonError: (error) => toSafeAgentError(error, secrets).message,\n\t}) as unknown as ChatTransport;\n\treturn {\n\t\tasync reconnectToStream(options) {\n\t\t\ttry {\n\t\t\t\tconst stream = await direct.reconnectToStream(options);\n\t\t\t\treturn stream ? boundAndRedactStream(stream, secrets, limits) : null;\n\t\t\t} catch (error) {\n\t\t\t\tthrow toSafeAgentError(error, secrets);\n\t\t\t}\n\t\t},\n\t\tasync sendMessages(options) {\n\t\t\ttry {\n\t\t\t\treturn boundAndRedactStream(await direct.sendMessages(options), secrets, limits);\n\t\t\t} catch (error) {\n\t\t\t\tthrow toSafeAgentError(error, secrets);\n\t\t\t}\n\t\t},\n\t};\n}\n\nfunction withoutAuthorizationHeader(headers?: Record): Record | undefined {\n\tif (!headers) return undefined;\n\treturn Object.fromEntries(Object.entries(headers).filter(([name]) => name.toLowerCase() !== 'authorization'));\n}\n\nfunction boundAndRedactStream(\n\tstream: ReadableStream,\n\tsecrets: readonly string[],\n\tlimits: ResolvedOpenAICompatibleDirectTransportLimits,\n) {\n\tconst reader = stream.getReader();\n\tlet chunks = 0;\n\tlet projectionBytes = 0;\n\treturn new ReadableStream({\n\t\tasync pull(controller) {\n\t\t\ttry {\n\t\t\t\tconst result = await reader.read();\n\t\t\t\tif (result.done) {\n\t\t\t\t\tcontroller.close();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tchunks += 1;\n\t\t\t\tif (chunks > limits.maxStreamChunks) {\n\t\t\t\t\tawait cancelForLimit(reader);\n\t\t\t\t\tcontroller.error(new Error('模型输出事件数量超过客户端安全限制。'));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst remaining = limits.maxStreamProjectionBytes - projectionBytes;\n\t\t\t\tconst measurement = measureAgentRuntimeValue(result.value, {\n\t\t\t\t\tmaxArrayItems: 128,\n\t\t\t\t\tmaxDataUrlBytes: Math.max(0, remaining),\n\t\t\t\t\tmaxDepth: 16,\n\t\t\t\t\tmaxNodes: 512,\n\t\t\t\t\tmaxObjectKeys: 64,\n\t\t\t\t\tmaxStringBytes: Math.max(0, remaining),\n\t\t\t\t});\n\t\t\t\tif (!measurement.success) {\n\t\t\t\t\tawait cancelForLimit(reader);\n\t\t\t\t\tcontroller.error(new Error('模型输出内容超过客户端安全限制。'));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tprojectionBytes += measurement.stringBytes + measurement.dataUrlBytes;\n\t\t\t\tif (projectionBytes > limits.maxStreamProjectionBytes) {\n\t\t\t\t\tawait cancelForLimit(reader);\n\t\t\t\t\tcontroller.error(new Error('模型输出内容超过客户端安全限制。'));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tcontroller.enqueue(result.value);\n\t\t\t} catch (error) {\n\t\t\t\tcontroller.error(toSafeAgentError(error, secrets));\n\t\t\t}\n\t\t},\n\t\tcancel(reason) {\n\t\t\treturn reader.cancel(reason);\n\t\t},\n\t});\n}\n\nasync function cancelForLimit(reader: ReadableStreamDefaultReader) {\n\tawait reader.cancel('stream limit exceeded').catch(() => undefined);\n}\n", "type": "registry:lib", "target": "@components/blocks/agent-ai-sdk/direct-transport.ts" }, { "path": "registry/default/blocks/agent-ai-sdk/generation-transport.ts", "content": "import type { ChatTransport, UIMessage } from 'ai';\n\nexport function bindAgentTransportGeneration(\n\ttransport: ChatTransport,\n\tgeneration: { current: number },\n): ChatTransport {\n\treturn {\n\t\tasync reconnectToStream(options) {\n\t\t\tconst current = generation.current;\n\t\t\ttry {\n\t\t\t\tconst stream = await transport.reconnectToStream(options);\n\t\t\t\tif (!stream) return null;\n\t\t\t\treturn bindStreamGeneration(stream, current, generation);\n\t\t\t} catch (error) {\n\t\t\t\tif (generation.current !== current) return null;\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t},\n\t\tasync sendMessages(options) {\n\t\t\tconst current = generation.current;\n\t\t\ttry {\n\t\t\t\tconst stream = await transport.sendMessages(options);\n\t\t\t\treturn bindStreamGeneration(stream, current, generation);\n\t\t\t} catch (error) {\n\t\t\t\tif (generation.current !== current) return emptyStream();\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t},\n\t};\n}\n\nfunction bindStreamGeneration(\n\tstream: ReadableStream,\n\tcurrent: number,\n\tgeneration: { current: number },\n): ReadableStream {\n\tconst reader = stream.getReader();\n\treturn new ReadableStream({\n\t\tasync pull(controller) {\n\t\t\tif (generation.current !== current) {\n\t\t\t\tawait reader.cancel('stale generation').catch(() => undefined);\n\t\t\t\tcontroller.close();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst result = await reader.read();\n\t\t\t\tif (generation.current !== current) {\n\t\t\t\t\tawait reader.cancel('stale generation').catch(() => undefined);\n\t\t\t\t\tcontroller.close();\n\t\t\t\t} else if (result.done) controller.close();\n\t\t\t\telse controller.enqueue(result.value);\n\t\t\t} catch (error) {\n\t\t\t\tif (generation.current !== current) controller.close();\n\t\t\t\telse controller.error(error);\n\t\t\t}\n\t\t},\n\t\tcancel(reason) {\n\t\t\treturn reader.cancel(reason);\n\t\t},\n\t});\n}\n\nfunction emptyStream(): ReadableStream {\n\treturn new ReadableStream({\n\t\tstart(controller) {\n\t\t\tcontroller.close();\n\t\t},\n\t});\n}\n", "type": "registry:lib", "target": "@components/blocks/agent-ai-sdk/generation-transport.ts" }, { "path": "registry/default/blocks/agent-ai-sdk/tool-approval.ts", "content": "export type AgentToolApprovalResponse = {\n\tapproved: boolean;\n\tid: string;\n\treason?: string;\n};\n\nexport type AgentToolApprovalResponder = (response: AgentToolApprovalResponse) => Promise;\n\nexport function createAgentToolApprovalResponder(options: {\n\tbeforeRespond?: (response: AgentToolApprovalResponse) => void | PromiseLike;\n\tonError: (error: unknown) => void;\n\trespond: (response: AgentToolApprovalResponse) => void | PromiseLike;\n}): AgentToolApprovalResponder {\n\tconst completed = new Set();\n\tconst pending = new Map>();\n\treturn (response) => {\n\t\tif (completed.has(response.id)) return Promise.resolve();\n\t\tconst current = pending.get(response.id);\n\t\tif (current) return current;\n\t\tconst request = Promise.resolve()\n\t\t\t.then(() => options.beforeRespond?.(response))\n\t\t\t.then(() => options.respond(response))\n\t\t\t.then(() => {\n\t\t\t\tcompleted.add(response.id);\n\t\t\t})\n\t\t\t.catch((error: unknown) => {\n\t\t\t\toptions.onError(error);\n\t\t\t\tthrow error;\n\t\t\t})\n\t\t\t.finally(() => {\n\t\t\t\tpending.delete(response.id);\n\t\t\t});\n\t\tpending.set(response.id, request);\n\t\treturn request;\n\t};\n}\n", "type": "registry:lib", "target": "@components/blocks/agent-ai-sdk/tool-approval.ts" }, { "path": "registry/default/blocks/agent-ai-sdk/connection-editor.tsx", "content": "'use client';\n\nimport { Check, Eye, EyeOff, RefreshCw, X } from 'lucide-react';\nimport type { ComponentPropsWithRef, FormEvent } from 'react';\nimport { useEffect, useId, useRef, useState } from 'react';\nimport { cn } from '@/lib/utils';\nimport type {\n\tConnectionValidationIssue,\n\tOpenAICompatibleConnectionConfig,\n\tOpenAICompatibleConnectionDraft,\n} from './connection-config';\nimport { validateOpenAICompatibleBaseUrl, validateOpenAICompatibleConnection } from './connection-config';\nimport { listOpenAICompatibleModels } from './model-list';\nimport { toSafeAgentError } from './safe-error';\n\nexport type OpenAICompatibleConnectionEditorMessages = {\n\tapiKey: string;\n\tapply: string;\n\tbaseUrl: string;\n\tclearApiKey: string;\n\thideApiKey: string;\n\tloadedModels: (count: number) => string;\n\tmodel: string;\n\tmodelList: string;\n\trefreshModels: string;\n\tshowApiKey: string;\n};\n\nexport const defaultOpenAICompatibleConnectionEditorMessages: OpenAICompatibleConnectionEditorMessages = {\n\tapiKey: 'API Key',\n\tapply: '应用连接',\n\tbaseUrl: 'Base URL',\n\tclearApiKey: '清除 API Key',\n\thideApiKey: '隐藏 API Key',\n\tloadedModels: (count) => `已加载 ${count} 个模型`,\n\tmodel: '模型',\n\tmodelList: '可用模型',\n\trefreshModels: '刷新模型',\n\tshowApiKey: '显示 API Key',\n};\n\nexport type OpenAICompatibleConnectionEditorProps = Omit, 'onSubmit'> & {\n\tapplied?: boolean;\n\tdraft: OpenAICompatibleConnectionDraft;\n\tfetch?: typeof globalThis.fetch;\n\tmessages?: Partial;\n\tonApply: (connection: OpenAICompatibleConnectionConfig) => void;\n\tonDraftChange: (draft: OpenAICompatibleConnectionDraft) => void;\n\trevealIdentity?: number | string;\n};\n\nexport function OpenAICompatibleConnectionEditor({\n\tapplied = false,\n\tclassName,\n\tdraft,\n\tfetch,\n\tmessages,\n\tonApply,\n\tonDraftChange,\n\trevealIdentity,\n\t...props\n}: OpenAICompatibleConnectionEditorProps) {\n\tconst copy = { ...defaultOpenAICompatibleConnectionEditorMessages, ...messages };\n\tconst modelListId = useId();\n\tconst refreshController = useRef(undefined);\n\tconst modelGeneration = useRef(0);\n\tconst [showKey, setShowKey] = useState(false);\n\tconst [models, setModels] = useState([]);\n\tconst [refreshing, setRefreshing] = useState(false);\n\tconst [issues, setIssues] = useState([]);\n\tconst [requestError, setRequestError] = useState();\n\n\tuseEffect(() => {\n\t\tsetShowKey(false);\n\t}, [revealIdentity]);\n\n\tuseEffect(() => {\n\t\tmodelGeneration.current += 1;\n\t\trefreshController.current?.abort();\n\t\trefreshController.current = undefined;\n\t\tsetModels([]);\n\t\tsetRefreshing(false);\n\t\tsetRequestError(undefined);\n\t}, [draft.apiKey, draft.baseUrl, draft.headers, revealIdentity]);\n\n\tuseEffect(\n\t\t() => () => {\n\t\t\tmodelGeneration.current += 1;\n\t\t\trefreshController.current?.abort();\n\t\t},\n\t\t[],\n\t);\n\n\tfunction update(\n\t\tkey: Key,\n\t\tvalue: OpenAICompatibleConnectionDraft[Key],\n\t) {\n\t\tsetIssues((current) => current.filter((issue) => issue.field !== key));\n\t\tsetRequestError(undefined);\n\t\tif (key === 'apiKey' || key === 'baseUrl' || key === 'headers') {\n\t\t\tmodelGeneration.current += 1;\n\t\t\trefreshController.current?.abort();\n\t\t\trefreshController.current = undefined;\n\t\t\tsetModels([]);\n\t\t\tsetRefreshing(false);\n\t\t}\n\t\tonDraftChange({ ...draft, [key]: value });\n\t}\n\n\tfunction apply(event: FormEvent) {\n\t\tevent.preventDefault();\n\t\tconst result = validateOpenAICompatibleConnection(draft);\n\t\tif (!result.success) {\n\t\t\tsetIssues(result.issues);\n\t\t\treturn;\n\t\t}\n\t\tsetIssues([]);\n\t\tsetRequestError(undefined);\n\t\tonApply(result.value);\n\t}\n\n\tasync function refreshModels() {\n\t\tconst baseUrl = validateOpenAICompatibleBaseUrl(draft.baseUrl);\n\t\tif (!baseUrl.success) {\n\t\t\tsetIssues([{ field: 'baseUrl', message: baseUrl.message }]);\n\t\t\treturn;\n\t\t}\n\t\trefreshController.current?.abort();\n\t\tconst controller = new AbortController();\n\t\tconst generation = modelGeneration.current + 1;\n\t\tmodelGeneration.current = generation;\n\t\trefreshController.current = controller;\n\t\tsetRefreshing(true);\n\t\tsetRequestError(undefined);\n\t\ttry {\n\t\t\tconst result = await listOpenAICompatibleModels(\n\t\t\t\t{ apiKey: draft.apiKey, baseUrl: baseUrl.value, headers: draft.headers },\n\t\t\t\t{ fetch, signal: controller.signal },\n\t\t\t);\n\t\t\tif (refreshController.current !== controller || modelGeneration.current !== generation) return;\n\t\t\tsetModels(result.map((model) => model.id));\n\t\t} catch (error) {\n\t\t\tif (!controller.signal.aborted) setRequestError(toSafeAgentError(error, [draft.apiKey ?? '']).message);\n\t\t} finally {\n\t\t\tif (refreshController.current === controller && modelGeneration.current === generation) {\n\t\t\t\trefreshController.current = undefined;\n\t\t\t\tsetRefreshing(false);\n\t\t\t}\n\t\t}\n\t}\n\n\tconst fieldError = (field: ConnectionValidationIssue['field']) =>\n\t\tissues.find((issue) => issue.field === field)?.message;\n\treturn (\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t{models.length > 0 ? (\n\t\t\t\t\n\t\t\t) : null}\n\t\t\t{requestError ? (\n\t\t\t\t
\n\t\t\t\t\t{requestError}\n\t\t\t\t
\n\t\t\t) : null}\n\t\t\t
\n\t\t\t\t void refreshModels()}\n\t\t\t\t>\n\t\t\t\t\t
\n\t\t\n\t);\n}\n\nfunction FieldError({ children }: { children?: string }) {\n\treturn children ? {children} : null;\n}\n", "type": "registry:component", "target": "@components/blocks/agent-ai-sdk/connection-editor.tsx" }, { "path": "registry/default/blocks/agent-ai-sdk/ai-sdk-agent-chat.tsx", "content": "'use client';\n\nimport { useChat } from '@ai-sdk/react';\nimport {\n\ttype ChatOnFinishCallback,\n\ttype ChatOnToolCallCallback,\n\ttype Instructions,\n\tlastAssistantMessageIsCompleteWithApprovalResponses,\n\ttype ToolSet,\n\ttype UIMessage,\n} from 'ai';\nimport { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';\nimport type { AgentMessagePartRenderContext } from '../../ui/agent-message';\nimport { AgentChat, type AgentChatProps, validateAgentRuntimeMessages } from '../agent-chat';\nimport type { OpenAICompatibleConnectionConfig } from './connection-config';\nimport { sameOpenAICompatibleConnection, validateOpenAICompatibleConnection } from './connection-config';\nimport { createOpenAICompatibleDirectTransport } from './direct-transport';\nimport type { OpenAICompatibleDirectTransportLimits } from './direct-transport-limits';\nimport {\n\tAgentRuntimeFileError,\n\ttype AgentRuntimeFileLimits,\n\tconvertAgentFilesToUIParts,\n\tresolveAgentRuntimeFileLimits,\n} from './file-parts';\nimport { bindAgentTransportGeneration } from './generation-transport';\nimport { isAbortError, toSafeAgentError } from './safe-error';\nimport {\n\ttype AgentToolApprovalResponder,\n\ttype AgentToolApprovalResponse,\n\tcreateAgentToolApprovalResponder,\n} from './tool-approval';\n\nexport type AiSdkAgentToolRenderContext = AgentMessagePartRenderContext & {\n\trespondToApproval: AgentToolApprovalResponder;\n};\n\nexport type AiSdkAgentChatProps = Omit<\n\tAgentChatProps,\n\t| 'connection'\n\t| 'disabled'\n\t| 'error'\n\t| 'files'\n\t| 'onFilesChange'\n\t| 'onRetry'\n\t| 'onSend'\n\t| 'onStop'\n\t| 'onTextChange'\n\t| 'renderTool'\n\t| 'status'\n\t| 'text'\n\t| 'value'\n> &\n\tAgentRuntimeFileLimits & {\n\t\tchatMessages?: readonly UIMessage[];\n\t\tconnection: OpenAICompatibleConnectionConfig;\n\t\tconnectionRevision?: number | string;\n\t\tconnectionSlot?: import('react').ReactNode;\n\t\tfetch?: typeof globalThis.fetch;\n\t\tinstructions?: Instructions;\n\t\tmaxSteps?: number;\n\t\tonError?: (error: Error) => void;\n\t\tonFinish?: ChatOnFinishCallback;\n\t\tonMessagesChange?: (messages: UIMessage[]) => void;\n\t\tonToolCall?: ChatOnToolCallCallback;\n\t\trenderTool?: (context: AiSdkAgentToolRenderContext) => import('react').ReactNode | undefined;\n\t\ttools?: ToolSet;\n\t\ttransportLimits?: OpenAICompatibleDirectTransportLimits;\n\t};\n\nexport function AiSdkAgentChat(props: AiSdkAgentChatProps) {\n\tconst validation = validateOpenAICompatibleConnection(props.connection);\n\tconst messageValidation = validateAgentRuntimeMessages(props.chatMessages ?? []);\n\tconst connection = validation.success ? validation.value : undefined;\n\tconst runtime = useRuntimeRevision({\n\t\tconnection,\n\t\texplicit: props.connectionRevision,\n\t\tfetch: props.fetch,\n\t\tinstructions: props.instructions,\n\t\tmaxSteps: props.maxSteps,\n\t\ttools: props.tools,\n\t\ttransportLimits: props.transportLimits,\n\t});\n\tif (!connection || !messageValidation.success) {\n\t\tconst connectionError = validation.success ? undefined : validation.issues[0]?.message;\n\t\treturn (\n\t\t\t undefined}\n\t\t\t\tonSend={() => undefined}\n\t\t\t\tonTextChange={() => undefined}\n\t\t\t/>\n\t\t);\n\t}\n\tif (runtime.pending) return null;\n\treturn ;\n}\n\nfunction AiSdkAgentChatSession({\n\tchatMessages,\n\tcomposerProps,\n\tconnection,\n\tconnectionRevision: _connectionRevision,\n\tconnectionSlot,\n\tfetch,\n\tinstructions,\n\tmaxFileSize,\n\tmaxFiles,\n\tmaxSteps,\n\tmaxTotalSize,\n\tmodel,\n\tonError,\n\tonFinish,\n\tonMessagesChange,\n\tonToolCall,\n\trenderTool,\n\ttools,\n\ttransportLimits,\n\t...chatProps\n}: AiSdkAgentChatProps) {\n\tconst generation = useRef(0);\n\tconst transport = useMemo(\n\t\t() =>\n\t\t\tbindAgentTransportGeneration(\n\t\t\t\tcreateOpenAICompatibleDirectTransport({\n\t\t\t\t\tconnection,\n\t\t\t\t\tfetch,\n\t\t\t\t\tinstructions,\n\t\t\t\t\tlimits: transportLimits,\n\t\t\t\t\tmaxSteps,\n\t\t\t\t\ttools,\n\t\t\t\t}),\n\t\t\t\tgeneration,\n\t\t\t),\n\t\t[connection, fetch, instructions, maxSteps, tools, transportLimits],\n\t);\n\tconst safeError = (error: unknown) =>\n\t\ttoSafeAgentError(error, [connection.apiKey ?? '', ...Object.values(connection.headers ?? {})]);\n\tconst { addToolApprovalResponse, clearError, error, messages, regenerate, sendMessage, setMessages, status, stop } =\n\t\tuseChat({\n\t\t\tmessages: chatMessages ? [...chatMessages] : [],\n\t\t\tonError: (value) => onError?.(safeError(value)),\n\t\t\tonFinish,\n\t\t\tonToolCall,\n\t\t\tsendAutomaticallyWhen: lastAssistantMessageIsCompleteWithApprovalResponses,\n\t\t\ttransport,\n\t\t});\n\tconst [text, setText] = useState('');\n\tconst [files, setFiles] = useState([]);\n\tconst [localError, setLocalError] = useState();\n\tconst mounted = useRef(false);\n\tconst fileController = useRef(undefined);\n\tconst appliedExternalMessages = useRef(chatMessages);\n\tconst externalMessagesTarget = useRef(null);\n\tconst handledMessages = useRef(messages);\n\tconst messagesRef = useRef(messages);\n\tconst onMessagesChangeRef = useRef(onMessagesChange);\n\tmessagesRef.current = messages;\n\tonMessagesChangeRef.current = onMessagesChange;\n\tconst approvalRuntime = useRef<{\n\t\tbeforeRespond: (response: AgentToolApprovalResponse) => void;\n\t\tonError: (error: unknown) => void;\n\t\trespond: (response: AgentToolApprovalResponse) => void | PromiseLike;\n\t} | null>(null);\n\tapprovalRuntime.current = {\n\t\tbeforeRespond: () => requireValidTranscript(messagesRef.current),\n\t\tonError: (value) => reportLocalError(value),\n\t\trespond: (response) => addToolApprovalResponse(response),\n\t};\n\tconst respondToApproval = useMemo(\n\t\t() =>\n\t\t\tcreateAgentToolApprovalResponder({\n\t\t\t\tbeforeRespond: (response) => approvalRuntime.current?.beforeRespond(response),\n\t\t\t\tonError: (value) => approvalRuntime.current?.onError(value),\n\t\t\t\trespond: (response) => approvalRuntime.current?.respond(response),\n\t\t\t}),\n\t\t[],\n\t);\n\tconst limits = resolveAgentRuntimeFileLimits({ maxFileSize, maxFiles, maxTotalSize });\n\n\tuseEffect(() => {\n\t\tif (chatMessages !== appliedExternalMessages.current) {\n\t\t\tappliedExternalMessages.current = chatMessages;\n\t\t\thandledMessages.current = messages;\n\t\t\tconst target = chatMessages ? [...chatMessages] : [];\n\t\t\tif (sameMessages(target, messages)) {\n\t\t\t\texternalMessagesTarget.current = null;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\texternalMessagesTarget.current = target;\n\t\t\tgeneration.current += 1;\n\t\t\tfileController.current?.abort();\n\t\t\tvoid stop();\n\t\t\tsetLocalError(undefined);\n\t\t\tsetMessages(target);\n\t\t\treturn;\n\t\t}\n\t\tconst target = externalMessagesTarget.current;\n\t\tif (target) {\n\t\t\thandledMessages.current = messages;\n\t\t\tif (sameMessages(target, messages)) externalMessagesTarget.current = null;\n\t\t\treturn;\n\t\t}\n\t\tif (messages === handledMessages.current) return;\n\t\thandledMessages.current = messages;\n\t\tonMessagesChangeRef.current?.(messages);\n\t}, [chatMessages, messages, setMessages, stop]);\n\n\tuseEffect(() => {\n\t\tmounted.current = true;\n\t\treturn () => {\n\t\t\tmounted.current = false;\n\t\t\tgeneration.current += 1;\n\t\t\tfileController.current?.abort();\n\t\t\tvoid stop();\n\t\t};\n\t}, [stop]);\n\n\tfunction reportLocalError(value: unknown) {\n\t\tif (!mounted.current) return;\n\t\tconst next = safeError(value);\n\t\tsetLocalError(next);\n\t\tonError?.(next);\n\t}\n\n\tasync function send(input: { files: File[]; text: string }) {\n\t\ttry {\n\t\t\trequireValidTranscript(messagesRef.current);\n\t\t} catch (value) {\n\t\t\treportLocalError(value);\n\t\t\treturn;\n\t\t}\n\t\tfileController.current?.abort();\n\t\tconst controller = new AbortController();\n\t\tconst currentGeneration = generation.current;\n\t\tfileController.current = controller;\n\t\ttry {\n\t\t\tconst parts = await convertAgentFilesToUIParts(input.files, limits, controller.signal);\n\t\t\tif (!mounted.current || fileController.current !== controller || generation.current !== currentGeneration) return;\n\t\t\tsetLocalError(undefined);\n\t\t\tclearError();\n\t\t\tconst request = sendMessage({ files: parts, text: input.text });\n\t\t\tsetText('');\n\t\t\tsetFiles([]);\n\t\t\tvoid request;\n\t\t} catch (value) {\n\t\t\tif (!isAbortError(value) && !(value instanceof AgentRuntimeFileError && value.code === 'aborted')) {\n\t\t\t\treportLocalError(value);\n\t\t\t}\n\t\t} finally {\n\t\t\tif (fileController.current === controller) fileController.current = undefined;\n\t\t}\n\t}\n\n\tfunction stopCurrent() {\n\t\tgeneration.current += 1;\n\t\tfileController.current?.abort();\n\t\tvoid stop();\n\t}\n\n\tfunction retry() {\n\t\ttry {\n\t\t\trequireValidTranscript(messagesRef.current);\n\t\t} catch (value) {\n\t\t\treportLocalError(value);\n\t\t\treturn;\n\t\t}\n\t\tsetLocalError(undefined);\n\t\tclearError();\n\t\tvoid regenerate();\n\t}\n\n\treturn (\n\t\t{connection.model}}\n\t\t\tstatus={status}\n\t\t\ttext={text}\n\t\t\tvalue={messages}\n\t\t\tonFilesChange={(next) => {\n\t\t\t\tsetLocalError(undefined);\n\t\t\t\tsetFiles(next);\n\t\t\t}}\n\t\t\tonRetry={error || localError ? retry : undefined}\n\t\t\tonSend={(input) => void send(input)}\n\t\t\tonStop={stopCurrent}\n\t\t\tonTextChange={(next) => {\n\t\t\t\tsetLocalError(undefined);\n\t\t\t\tif (status === 'error') clearError();\n\t\t\t\tsetText(next);\n\t\t\t}}\n\t\t\trenderTool={renderTool ? (context) => renderTool({ ...context, respondToApproval }) : undefined}\n\t\t/>\n\t);\n}\n\nfunction requireValidTranscript(messages: readonly UIMessage[]) {\n\tconst validation = validateAgentRuntimeMessages(messages);\n\tif (!validation.success) throw new Error(validation.message);\n}\n\nfunction presenterProps(\n\tprops: AiSdkAgentChatProps,\n): Omit<\n\tAgentChatProps,\n\t| 'connection'\n\t| 'disabled'\n\t| 'error'\n\t| 'files'\n\t| 'onFilesChange'\n\t| 'onRetry'\n\t| 'onSend'\n\t| 'onStop'\n\t| 'onTextChange'\n\t| 'renderTool'\n\t| 'status'\n\t| 'text'\n\t| 'value'\n> {\n\tconst {\n\t\tchatMessages: _chatMessages,\n\t\tconnection: _connection,\n\t\tconnectionRevision: _connectionRevision,\n\t\tconnectionSlot: _connectionSlot,\n\t\tfetch: _fetch,\n\t\tinstructions: _instructions,\n\t\tmaxFileSize: _maxFileSize,\n\t\tmaxFiles: _maxFiles,\n\t\tmaxSteps: _maxSteps,\n\t\tmaxTotalSize: _maxTotalSize,\n\t\tonError: _onError,\n\t\tonFinish: _onFinish,\n\t\tonMessagesChange: _onMessagesChange,\n\t\tonToolCall: _onToolCall,\n\t\trenderTool: _renderTool,\n\t\ttools: _tools,\n\t\ttransportLimits: _transportLimits,\n\t\t...rest\n\t} = props;\n\treturn rest;\n}\n\ntype RuntimeIdentity = {\n\tconnection?: OpenAICompatibleConnectionConfig;\n\texplicit?: number | string;\n\tfetch?: typeof globalThis.fetch;\n\tinstructions?: Instructions;\n\tmaxSteps?: number;\n\ttools?: ToolSet;\n\ttransportLimits?: OpenAICompatibleDirectTransportLimits;\n};\n\nfunction useRuntimeRevision(identity: RuntimeIdentity): { pending: boolean; revision: number } {\n\tconst [committed, setCommitted] = useState(() => ({ identity: snapshotRuntimeIdentity(identity), revision: 1 }));\n\tconst pending = !sameRuntimeIdentity(committed.identity, identity);\n\tuseLayoutEffect(() => {\n\t\tif (!pending) return;\n\t\tsetCommitted((current) =>\n\t\t\tsameRuntimeIdentity(current.identity, identity)\n\t\t\t\t? current\n\t\t\t\t: { identity: snapshotRuntimeIdentity(identity), revision: current.revision + 1 },\n\t\t);\n\t}, [identity, pending]);\n\treturn { pending, revision: committed.revision };\n}\n\nfunction sameRuntimeIdentity(left: RuntimeIdentity, right: RuntimeIdentity): boolean {\n\treturn (\n\t\tleft.explicit === right.explicit &&\n\t\tleft.fetch === right.fetch &&\n\t\tleft.instructions === right.instructions &&\n\t\tleft.maxSteps === right.maxSteps &&\n\t\tleft.tools === right.tools &&\n\t\tsameTransportLimits(left.transportLimits, right.transportLimits) &&\n\t\t((!left.connection && !right.connection) ||\n\t\t\t(Boolean(left.connection) &&\n\t\t\t\tBoolean(right.connection) &&\n\t\t\t\tsameOpenAICompatibleConnection(left.connection!, right.connection!)))\n\t);\n}\n\nfunction sameTransportLimits(\n\tleft: OpenAICompatibleDirectTransportLimits | undefined,\n\tright: OpenAICompatibleDirectTransportLimits | undefined,\n): boolean {\n\tif (!left || !right) return left === right;\n\treturn (\n\t\tleft.maxOutputTokens === right.maxOutputTokens &&\n\t\tleft.maxResponseBytes === right.maxResponseBytes &&\n\t\tleft.maxResponseChunks === right.maxResponseChunks &&\n\t\tleft.maxRetries === right.maxRetries &&\n\t\tleft.maxStreamChunks === right.maxStreamChunks &&\n\t\tleft.maxStreamProjectionBytes === right.maxStreamProjectionBytes &&\n\t\tleft.timeout?.chunkMs === right.timeout?.chunkMs &&\n\t\tleft.timeout?.stepMs === right.timeout?.stepMs &&\n\t\tleft.timeout?.toolMs === right.timeout?.toolMs &&\n\t\tleft.timeout?.totalMs === right.timeout?.totalMs\n\t);\n}\n\nfunction sameMessages(left: readonly UIMessage[], right: readonly UIMessage[]): boolean {\n\treturn left.length === right.length && left.every((message, index) => message === right[index]);\n}\n\nfunction snapshotRuntimeIdentity(identity: RuntimeIdentity): RuntimeIdentity {\n\treturn {\n\t\t...identity,\n\t\tconnection: identity.connection\n\t\t\t? {\n\t\t\t\t\t...identity.connection,\n\t\t\t\t\theaders: identity.connection.headers ? { ...identity.connection.headers } : undefined,\n\t\t\t\t}\n\t\t\t: undefined,\n\t\ttransportLimits: identity.transportLimits\n\t\t\t? {\n\t\t\t\t\t...identity.transportLimits,\n\t\t\t\t\ttimeout: identity.transportLimits.timeout ? { ...identity.transportLimits.timeout } : undefined,\n\t\t\t\t}\n\t\t\t: undefined,\n\t};\n}\n", "type": "registry:component", "target": "@components/blocks/agent-ai-sdk/ai-sdk-agent-chat.tsx" }, { "path": "registry/default/blocks/agent-ai-sdk/agent-chat-playground.tsx", "content": "'use client';\n\nimport type { Instructions, ToolSet } from 'ai';\nimport type { ComponentPropsWithRef } from 'react';\nimport { useState } from 'react';\nimport { cn } from '@/lib/utils';\nimport { AgentChat } from '../agent-chat';\nimport { AiSdkAgentChat } from './ai-sdk-agent-chat';\nimport type { OpenAICompatibleConnectionConfig, OpenAICompatibleConnectionDraft } from './connection-config';\nimport { OpenAICompatibleConnectionEditor } from './connection-editor';\n\nexport type AgentChatPlaygroundProps = ComponentPropsWithRef<'section'> & {\n\tfetch?: typeof globalThis.fetch;\n\tinstructions?: Instructions;\n\tmaxSteps?: number;\n\ttools?: ToolSet;\n};\n\nconst blankConnection: OpenAICompatibleConnectionDraft = {\n\tapiKey: '',\n\tbaseUrl: '',\n\theaders: {},\n\tmodel: '',\n};\n\nexport function AgentChatPlayground({\n\tclassName,\n\tfetch,\n\tinstructions,\n\tmaxSteps,\n\ttools,\n\t...props\n}: AgentChatPlaygroundProps) {\n\tconst [draft, setDraft] = useState(() => ({ ...blankConnection }));\n\tconst [applied, setApplied] = useState();\n\tconst [revision, setRevision] = useState(0);\n\treturn (\n\t\t\n\t\t\t {\n\t\t\t\t\tsetApplied({ ...connection, headers: connection.headers ? { ...connection.headers } : undefined });\n\t\t\t\t\tsetRevision((value) => value + 1);\n\t\t\t\t}}\n\t\t\t\tonDraftChange={setDraft}\n\t\t\t/>\n\t\t\t
\n\t\t\t\t{applied ? (\n\t\t\t\t\t配置已应用}\n\t\t\t\t\t\tfetch={fetch}\n\t\t\t\t\t\theader={

对话

}\n\t\t\t\t\t\tinstructions={instructions}\n\t\t\t\t\t\tmaxSteps={maxSteps}\n\t\t\t\t\t\ttools={tools}\n\t\t\t\t\t/>\n\t\t\t\t) : (\n\t\t\t\t\t undefined}\n\t\t\t\t\t\tonSend={() => undefined}\n\t\t\t\t\t\tonTextChange={() => undefined}\n\t\t\t\t\t/>\n\t\t\t\t)}\n\t\t\t
\n\t\t\n\t);\n}\n", "type": "registry:component", "target": "@components/blocks/agent-ai-sdk/agent-chat-playground.tsx" }, { "path": "registry/default/blocks/agent-ai-sdk/index.ts", "content": "export * from './agent-chat-playground';\nexport * from './ai-sdk-agent-chat';\nexport * from './connection-config';\nexport * from './connection-editor';\nexport * from './direct-transport';\nexport * from './direct-transport-limits';\nexport * from './file-parts';\nexport * from './generation-transport';\nexport * from './model-list';\nexport * from './safe-error';\nexport * from './tool-approval';\n", "type": "registry:component", "target": "@components/blocks/agent-ai-sdk/index.ts" } ], "type": "registry:block" }