@aparte/provider-ai-sdk 0.7.1 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -2
- package/dist/index.d.ts +3 -3
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -6,12 +6,20 @@ one de-facto-standard format; everything else (Anthropic, Google, Bedrock, 25+ v
|
|
|
6
6
|
the AI SDK ecosystem through this bridge. You bring your `@ai-sdk/*` package, hand its model to
|
|
7
7
|
`createAiSdkProvider`, and the bridge maps `streamText`'s `fullStream` to aparté's events.
|
|
8
8
|
|
|
9
|
+
```bash
|
|
10
|
+
npm install @aparte/provider-ai-sdk @aparte/core ai @ai-sdk/anthropic
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
`ai` and your chosen `@ai-sdk/*` vendor package are **peer dependencies** — you pick the
|
|
14
|
+
versions, and only the vendors you actually use end up in your bundle. `@aparte/core` is a
|
|
15
|
+
peer too.
|
|
16
|
+
|
|
9
17
|
```ts
|
|
10
18
|
import { createAnthropic } from '@ai-sdk/anthropic';
|
|
11
19
|
import { createAiSdkProvider } from '@aparte/provider-ai-sdk';
|
|
12
|
-
import {
|
|
20
|
+
import { aparteGlobalConfig } from '@aparte/core';
|
|
13
21
|
|
|
14
|
-
|
|
22
|
+
aparteGlobalConfig.registerAIProvider(createAiSdkProvider({
|
|
15
23
|
id: 'anthropic',
|
|
16
24
|
name: 'Anthropic',
|
|
17
25
|
models: [{ id: 'claude-sonnet-4-5', name: 'Claude Sonnet 4.5' }],
|
package/dist/index.d.ts
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* import { createAnthropic } from '@ai-sdk/anthropic';
|
|
13
13
|
* import { createAiSdkProvider } from '@aparte/provider-ai-sdk';
|
|
14
14
|
*
|
|
15
|
-
*
|
|
15
|
+
* aparteGlobalConfig.registerAIProvider(createAiSdkProvider({
|
|
16
16
|
* id: 'anthropic',
|
|
17
17
|
* name: 'Anthropic',
|
|
18
18
|
* models: [{ id: 'claude-sonnet-4-5', name: 'Claude Sonnet 4.5' }],
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
*
|
|
32
32
|
* The bridge is an `AparteAIProvider` with a `chat()` (it owns its I/O via the
|
|
33
33
|
* SDK — same shape as `@aparte/provider-transformers`), driven by
|
|
34
|
-
* `
|
|
34
|
+
* `AparteDirectTransport`'s delegation branch, which forwards `ctx.signal` so a user
|
|
35
35
|
* "stop" aborts the underlying vendor call.
|
|
36
36
|
*
|
|
37
37
|
* NOTE (loop contract): the bridge never sees `toolChoice: { name, input }` —
|
|
@@ -91,7 +91,7 @@ export declare function fullStreamToAparteEvents(fullStream: AsyncIterable<{
|
|
|
91
91
|
} & Record<string, unknown>>): ReadableStream<AparteStreamEvent>;
|
|
92
92
|
/**
|
|
93
93
|
* Wrap an AI SDK model factory into an `AparteAIProvider`. The returned provider
|
|
94
|
-
* owns its I/O through the SDK (`chat()` shape — `
|
|
94
|
+
* owns its I/O through the SDK (`chat()` shape — `AparteDirectTransport` delegates
|
|
95
95
|
* to it and forwards the abort signal).
|
|
96
96
|
*/
|
|
97
97
|
export declare function createAiSdkProvider(opts: AiSdkProviderOptions): AparteAIProvider;
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["/**\n * @aparte/provider-ai-sdk — bridge any Vercel AI SDK model into aparté.\n *\n * aparté's own wire concern is deliberately tiny: `@aparte/provider-openai-compat`\n * covers the one de-facto-standard format. EVERYTHING else (Anthropic, Google,\n * Bedrock, 25+ vendors) rides the AI SDK ecosystem through this bridge: you\n * bring your `@ai-sdk/*` package and hand its model to `createAiSdkProvider`;\n * the bridge runs `streamText` (single step — aparté's agent loop owns the\n * multi-turn) and maps `fullStream` parts to aparté's `AparteStreamEvent`s.\n *\n * ```ts\n * import { createAnthropic } from '@ai-sdk/anthropic';\n * import { createAiSdkProvider } from '@aparte/provider-ai-sdk';\n *\n * AparteConfig.registerAIProvider(createAiSdkProvider({\n * id: 'anthropic',\n * name: 'Anthropic',\n * models: [{ id: 'claude-sonnet-4-5', name: 'Claude Sonnet 4.5' }],\n * languageModel: (modelId, auth) =>\n * createAnthropic({\n * apiKey: typeof auth === 'string' ? auth : auth?.['apiKey'],\n * headers: { 'anthropic-dangerous-direct-browser-access': 'true' }, // BYOK\n * })(modelId),\n * }));\n * ```\n *\n * DEPENDENCY POLICY: `ai` is a **peerDependency pinned to the verified major**\n * (`^7`). The AI SDK moves fast (v5→v7 in about a year); this bridge is the\n * ONLY aparté module touching its types, so a breaking major costs one small\n * package bump — widened per-major after verification, never speculatively.\n *\n * The bridge is an `AparteAIProvider` with a `chat()` (it owns its I/O via the\n * SDK — same shape as `@aparte/provider-transformers`), driven by\n * `DirectTransport`'s delegation branch, which forwards `ctx.signal` so a user\n * \"stop\" aborts the underlying vendor call.\n *\n * NOTE (loop contract): the bridge never sees `toolChoice: { name, input }` —\n * the agent loop (engine `runStreamAgent` / core `_streamLoop`) executes that\n * synthetic call itself and strips `toolChoice` before the transport call.\n * Only `'auto' | 'none' | { name }` reach this module.\n */\n\nimport type {\n AparteAIProvider,\n AparteAIModel,\n AparteAIProviderConfigSchema,\n AparteChatRequest,\n AparteChatResponse,\n AparteChatMessage,\n AparteStreamEvent,\n AparteUsage,\n} from '@aparte/core';\nimport { contentToText } from '@aparte/core';\nimport { streamText, jsonSchema, tool } from 'ai';\nimport type { LanguageModel, ModelMessage, ToolSet, ToolChoice } from 'ai';\n\n// ─── Options ─────────────────────────────────────────────────────────────────\n\nexport interface AiSdkProviderOptions {\n /** Provider id used across aparté (key resolution, model picker, events). */\n id: string;\n /**\n * Resolve the AI SDK model for a chat call. `auth` is the key/config the\n * aparté key-resolver produced — rebuild the vendor provider per call for\n * UI-driven BYOK, or ignore it if your factory already carries the key.\n */\n languageModel: (modelId: string, auth?: string | Record<string, string>) => LanguageModel;\n /** Display name (defaults to `id`). */\n name?: string;\n /** Brand icon (SVG string / data URI / icon-provider key). */\n icon?: string;\n /** Brand color. */\n color?: string;\n /** Short tag line. */\n description?: string;\n /** Where the user gets a key. */\n helpUrl?: string;\n /** Whether the vendor offers free models. */\n hasFreeModels?: boolean;\n /** Model runs locally (no key expected). */\n isLocal?: boolean;\n /** Model list — **consumer data** (the AI SDK has no model-listing API). */\n models?: AparteAIModel[];\n /** Override the default apiKey settings schema. */\n configSchema?: AparteAIProviderConfigSchema;\n}\n\n// ─── aparté ⇄ AI SDK shaping ─────────────────────────────────────────────────\n\n/**\n * AparteChatMessage[] → AI SDK ModelMessage[]. Handles aparté's `tool_call` /\n * `tool_result` envelope (assistant tool-call parts + tool-role results); the\n * tool name for a result is recovered from the preceding envelope.\n */\nexport function toModelMessages(messages: AparteChatMessage[]): ModelMessage[] {\n // toolCallId → toolName (results reference calls by id only).\n const toolNames = new Map<string, string>();\n for (const msg of messages) {\n if (msg.role === 'tool_call') {\n for (const tc of msg.toolCalls ?? []) toolNames.set(tc.id, tc.name);\n }\n }\n\n const out: ModelMessage[] = [];\n for (const msg of messages) {\n if (msg.role === 'tool_call') {\n out.push({\n role: 'assistant',\n content: [\n ...(msg.precedingText ? [{ type: 'text' as const, text: msg.precedingText }] : []),\n ...(msg.toolCalls ?? []).map(tc => ({\n type: 'tool-call' as const,\n toolCallId: tc.id,\n toolName: tc.name,\n input: tc.input,\n })),\n ],\n });\n continue;\n }\n if (msg.role === 'tool_result') {\n out.push({\n role: 'tool',\n content: [{\n type: 'tool-result',\n toolCallId: msg.toolCallId ?? '',\n toolName: toolNames.get(msg.toolCallId ?? '') ?? 'unknown',\n output: { type: 'text', value: contentToText(msg.content) },\n }],\n });\n continue;\n }\n if (msg.role === 'system') {\n out.push({ role: 'system', content: contentToText(msg.content) });\n continue;\n }\n if (msg.role === 'assistant') {\n out.push({ role: 'assistant', content: contentToText(msg.content) });\n continue;\n }\n // user (default) — keep multimodal parts.\n if (typeof msg.content === 'string') {\n out.push({ role: 'user', content: msg.content });\n } else {\n out.push({\n role: 'user',\n content: msg.content.map(p => {\n if (p.type === 'text') return { type: 'text' as const, text: p.text };\n if (p.type === 'image') return { type: 'image' as const, image: p.image };\n return { type: 'text' as const, text: '' }; // AparteFilePart — not bridged\n }),\n });\n }\n }\n return out;\n}\n\n/** AparteTool[] → AI SDK ToolSet — declaration only, NO `execute` (aparté's loop runs tools). */\nexport function toToolSet(tools: NonNullable<AparteChatRequest['tools']>): ToolSet {\n return Object.fromEntries(tools.map(t => [\n t.name,\n tool({\n description: t.description,\n inputSchema: jsonSchema(t.inputSchema as Parameters<typeof jsonSchema>[0]),\n }),\n ]));\n}\n\n/** aparté toolChoice → AI SDK toolChoice (the synthetic {name,input} never reaches here). */\nexport function toToolChoice(choice: AparteChatRequest['toolChoice']): ToolChoice<ToolSet> | undefined {\n if (choice === 'auto' || choice === 'none') return choice;\n if (choice && typeof choice === 'object') return { type: 'tool', toolName: choice.name };\n return undefined;\n}\n\n/** fullStream `finish.totalUsage` → AparteUsage. */\nfunction toAparteUsage(u: { inputTokens?: number; outputTokens?: number; totalTokens?: number; inputTokenDetails?: { cacheReadTokens?: number } }): AparteUsage {\n return {\n inputTokens: u.inputTokens ?? 0,\n outputTokens: u.outputTokens ?? 0,\n totalTokens: u.totalTokens,\n cacheReadTokens: u.inputTokenDetails?.cacheReadTokens,\n };\n}\n\n/**\n * Map the AI SDK `fullStream` to aparté's event stream:\n * `text-delta`→text · `reasoning-delta`→thinking · `tool-call`→tool_use ·\n * `finish`→done{usage} · `error`→error. Everything else (step markers,\n * tool-input deltas, sources, files) is dropped — aparté's loop consumes whole\n * tool calls, not input deltas.\n */\nexport function fullStreamToAparteEvents(\n fullStream: AsyncIterable<{ type: string } & Record<string, unknown>>,\n): ReadableStream<AparteStreamEvent> {\n // Held so `cancel()` can signal the AI SDK's iterator to stop (calling\n // `.return()` propagates cancellation the same way breaking a `for await`\n // loop would), instead of leaving `start()` draining `fullStream` to its\n // natural end after the consumer has already walked away.\n let iterator: AsyncIterator<{ type: string } & Record<string, unknown>> | undefined;\n\n return new ReadableStream<AparteStreamEvent>({\n async start(controller) {\n iterator = fullStream[Symbol.asyncIterator]();\n try {\n while (true) {\n const { value: part, done } = await iterator.next();\n if (done) break;\n\n switch (part.type) {\n case 'text-delta':\n controller.enqueue({ type: 'text', delta: part['text'] as string });\n break;\n case 'reasoning-delta':\n controller.enqueue({ type: 'thinking', delta: part['text'] as string });\n break;\n case 'tool-call':\n controller.enqueue({\n type: 'tool_use',\n id: part['toolCallId'] as string,\n name: part['toolName'] as string,\n input: (part['input'] ?? {}) as Record<string, unknown>,\n });\n break;\n case 'finish':\n controller.enqueue({ type: 'done', usage: toAparteUsage(part['totalUsage'] as Parameters<typeof toAparteUsage>[0]) });\n // Terminal: stop reading `fullStream` so a stray second\n // finish/error part (if the SDK ever emits one) can't\n // enqueue past the done event.\n return;\n case 'error': {\n const err = part['error'];\n controller.enqueue({ type: 'error', message: err instanceof Error ? err.message : String(err) });\n return;\n }\n // 'abort', step markers, tool-input-* deltas, sources… → dropped.\n }\n }\n } catch (err: unknown) {\n // AbortError surfaces here when ctx.signal fires mid-stream: the\n // consumer (agent loop) cancelled on purpose — end quietly.\n if ((err as { name?: string })?.name !== 'AbortError') {\n controller.enqueue({ type: 'error', message: (err as Error | undefined)?.message ?? 'Stream error' });\n }\n } finally {\n controller.close();\n }\n },\n async cancel(reason) {\n await iterator?.return?.(reason);\n },\n });\n}\n\n// ─── The provider factory ────────────────────────────────────────────────────\n\nconst DEFAULT_CONFIG_SCHEMA = (opts: AiSdkProviderOptions): AparteAIProviderConfigSchema => ({\n fields: opts.isLocal\n ? []\n : [{ id: 'apiKey', type: 'password', label: 'API Key', required: true }],\n});\n\n/**\n * Wrap an AI SDK model factory into an `AparteAIProvider`. The returned provider\n * owns its I/O through the SDK (`chat()` shape — `DirectTransport` delegates\n * to it and forwards the abort signal).\n */\nexport function createAiSdkProvider(opts: AiSdkProviderOptions): AparteAIProvider {\n const displayName = opts.name ?? opts.id;\n\n return {\n id: opts.id,\n\n getMetadata() {\n return {\n id: opts.id,\n name: displayName,\n icon: opts.icon,\n color: opts.color,\n description: opts.description,\n helpUrl: opts.helpUrl,\n hasFreeModels: opts.hasFreeModels,\n isLocal: opts.isLocal,\n configSchema: opts.configSchema ?? DEFAULT_CONFIG_SCHEMA(opts),\n };\n },\n\n getModels(): AparteAIModel[] {\n return opts.models ?? [];\n },\n\n async chat(\n request: AparteChatRequest,\n auth?: string | Record<string, string>,\n ctx?: { providerId: string; signal?: AbortSignal },\n ): Promise<AparteChatResponse> {\n const model = opts.languageModel(request.modelId ?? '', auth);\n\n const result = streamText({\n model,\n messages: toModelMessages(request.messages),\n temperature: request.temperature,\n maxOutputTokens: request.maxTokens,\n seed: request.seed,\n abortSignal: ctx?.signal,\n ...(request.tools?.length\n ? { tools: toToolSet(request.tools), toolChoice: toToolChoice(request.toolChoice) ?? 'auto' }\n : {}),\n });\n\n if (request.stream === false) {\n return await result.text;\n }\n return fullStreamToAparteEvents(result.fullStream);\n },\n };\n}\n\nexport type { AparteAIProvider, AparteAIModel } from '@aparte/core';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["/**\n * @aparte/provider-ai-sdk — bridge any Vercel AI SDK model into aparté.\n *\n * aparté's own wire concern is deliberately tiny: `@aparte/provider-openai-compat`\n * covers the one de-facto-standard format. EVERYTHING else (Anthropic, Google,\n * Bedrock, 25+ vendors) rides the AI SDK ecosystem through this bridge: you\n * bring your `@ai-sdk/*` package and hand its model to `createAiSdkProvider`;\n * the bridge runs `streamText` (single step — aparté's agent loop owns the\n * multi-turn) and maps `fullStream` parts to aparté's `AparteStreamEvent`s.\n *\n * ```ts\n * import { createAnthropic } from '@ai-sdk/anthropic';\n * import { createAiSdkProvider } from '@aparte/provider-ai-sdk';\n *\n * aparteGlobalConfig.registerAIProvider(createAiSdkProvider({\n * id: 'anthropic',\n * name: 'Anthropic',\n * models: [{ id: 'claude-sonnet-4-5', name: 'Claude Sonnet 4.5' }],\n * languageModel: (modelId, auth) =>\n * createAnthropic({\n * apiKey: typeof auth === 'string' ? auth : auth?.['apiKey'],\n * headers: { 'anthropic-dangerous-direct-browser-access': 'true' }, // BYOK\n * })(modelId),\n * }));\n * ```\n *\n * DEPENDENCY POLICY: `ai` is a **peerDependency pinned to the verified major**\n * (`^7`). The AI SDK moves fast (v5→v7 in about a year); this bridge is the\n * ONLY aparté module touching its types, so a breaking major costs one small\n * package bump — widened per-major after verification, never speculatively.\n *\n * The bridge is an `AparteAIProvider` with a `chat()` (it owns its I/O via the\n * SDK — same shape as `@aparte/provider-transformers`), driven by\n * `AparteDirectTransport`'s delegation branch, which forwards `ctx.signal` so a user\n * \"stop\" aborts the underlying vendor call.\n *\n * NOTE (loop contract): the bridge never sees `toolChoice: { name, input }` —\n * the agent loop (engine `runStreamAgent` / core `_streamLoop`) executes that\n * synthetic call itself and strips `toolChoice` before the transport call.\n * Only `'auto' | 'none' | { name }` reach this module.\n */\n\nimport type {\n AparteAIProvider,\n AparteAIModel,\n AparteAIProviderConfigSchema,\n AparteChatRequest,\n AparteChatResponse,\n AparteChatMessage,\n AparteStreamEvent,\n AparteUsage,\n} from '@aparte/core';\nimport { contentToText } from '@aparte/core';\nimport { streamText, jsonSchema, tool } from 'ai';\nimport type { LanguageModel, ModelMessage, ToolSet, ToolChoice } from 'ai';\n\n// ─── Options ─────────────────────────────────────────────────────────────────\n\nexport interface AiSdkProviderOptions {\n /** Provider id used across aparté (key resolution, model picker, events). */\n id: string;\n /**\n * Resolve the AI SDK model for a chat call. `auth` is the key/config the\n * aparté key-resolver produced — rebuild the vendor provider per call for\n * UI-driven BYOK, or ignore it if your factory already carries the key.\n */\n languageModel: (modelId: string, auth?: string | Record<string, string>) => LanguageModel;\n /** Display name (defaults to `id`). */\n name?: string;\n /** Brand icon (SVG string / data URI / icon-provider key). */\n icon?: string;\n /** Brand color. */\n color?: string;\n /** Short tag line. */\n description?: string;\n /** Where the user gets a key. */\n helpUrl?: string;\n /** Whether the vendor offers free models. */\n hasFreeModels?: boolean;\n /** Model runs locally (no key expected). */\n isLocal?: boolean;\n /** Model list — **consumer data** (the AI SDK has no model-listing API). */\n models?: AparteAIModel[];\n /** Override the default apiKey settings schema. */\n configSchema?: AparteAIProviderConfigSchema;\n}\n\n// ─── aparté ⇄ AI SDK shaping ─────────────────────────────────────────────────\n\n/**\n * AparteChatMessage[] → AI SDK ModelMessage[]. Handles aparté's `tool_call` /\n * `tool_result` envelope (assistant tool-call parts + tool-role results); the\n * tool name for a result is recovered from the preceding envelope.\n */\nexport function toModelMessages(messages: AparteChatMessage[]): ModelMessage[] {\n // toolCallId → toolName (results reference calls by id only).\n const toolNames = new Map<string, string>();\n for (const msg of messages) {\n if (msg.role === 'tool_call') {\n for (const tc of msg.toolCalls ?? []) toolNames.set(tc.id, tc.name);\n }\n }\n\n const out: ModelMessage[] = [];\n for (const msg of messages) {\n if (msg.role === 'tool_call') {\n out.push({\n role: 'assistant',\n content: [\n ...(msg.precedingText ? [{ type: 'text' as const, text: msg.precedingText }] : []),\n ...(msg.toolCalls ?? []).map(tc => ({\n type: 'tool-call' as const,\n toolCallId: tc.id,\n toolName: tc.name,\n input: tc.input,\n })),\n ],\n });\n continue;\n }\n if (msg.role === 'tool_result') {\n out.push({\n role: 'tool',\n content: [{\n type: 'tool-result',\n toolCallId: msg.toolCallId ?? '',\n toolName: toolNames.get(msg.toolCallId ?? '') ?? 'unknown',\n output: { type: 'text', value: contentToText(msg.content) },\n }],\n });\n continue;\n }\n if (msg.role === 'system') {\n out.push({ role: 'system', content: contentToText(msg.content) });\n continue;\n }\n if (msg.role === 'assistant') {\n out.push({ role: 'assistant', content: contentToText(msg.content) });\n continue;\n }\n // user (default) — keep multimodal parts.\n if (typeof msg.content === 'string') {\n out.push({ role: 'user', content: msg.content });\n } else {\n out.push({\n role: 'user',\n content: msg.content.map(p => {\n if (p.type === 'text') return { type: 'text' as const, text: p.text };\n if (p.type === 'image') return { type: 'image' as const, image: p.image };\n return { type: 'text' as const, text: '' }; // AparteFilePart — not bridged\n }),\n });\n }\n }\n return out;\n}\n\n/** AparteTool[] → AI SDK ToolSet — declaration only, NO `execute` (aparté's loop runs tools). */\nexport function toToolSet(tools: NonNullable<AparteChatRequest['tools']>): ToolSet {\n return Object.fromEntries(tools.map(t => [\n t.name,\n tool({\n description: t.description,\n inputSchema: jsonSchema(t.inputSchema as Parameters<typeof jsonSchema>[0]),\n }),\n ]));\n}\n\n/** aparté toolChoice → AI SDK toolChoice (the synthetic {name,input} never reaches here). */\nexport function toToolChoice(choice: AparteChatRequest['toolChoice']): ToolChoice<ToolSet> | undefined {\n if (choice === 'auto' || choice === 'none') return choice;\n if (choice && typeof choice === 'object') return { type: 'tool', toolName: choice.name };\n return undefined;\n}\n\n/** fullStream `finish.totalUsage` → AparteUsage. */\nfunction toAparteUsage(u: { inputTokens?: number; outputTokens?: number; totalTokens?: number; inputTokenDetails?: { cacheReadTokens?: number } }): AparteUsage {\n return {\n inputTokens: u.inputTokens ?? 0,\n outputTokens: u.outputTokens ?? 0,\n totalTokens: u.totalTokens,\n cacheReadTokens: u.inputTokenDetails?.cacheReadTokens,\n };\n}\n\n/**\n * Map the AI SDK `fullStream` to aparté's event stream:\n * `text-delta`→text · `reasoning-delta`→thinking · `tool-call`→tool_use ·\n * `finish`→done{usage} · `error`→error. Everything else (step markers,\n * tool-input deltas, sources, files) is dropped — aparté's loop consumes whole\n * tool calls, not input deltas.\n */\nexport function fullStreamToAparteEvents(\n fullStream: AsyncIterable<{ type: string } & Record<string, unknown>>,\n): ReadableStream<AparteStreamEvent> {\n // Held so `cancel()` can signal the AI SDK's iterator to stop (calling\n // `.return()` propagates cancellation the same way breaking a `for await`\n // loop would), instead of leaving `start()` draining `fullStream` to its\n // natural end after the consumer has already walked away.\n let iterator: AsyncIterator<{ type: string } & Record<string, unknown>> | undefined;\n\n return new ReadableStream<AparteStreamEvent>({\n async start(controller) {\n iterator = fullStream[Symbol.asyncIterator]();\n try {\n while (true) {\n const { value: part, done } = await iterator.next();\n if (done) break;\n\n switch (part.type) {\n case 'text-delta':\n controller.enqueue({ type: 'text', delta: part['text'] as string });\n break;\n case 'reasoning-delta':\n controller.enqueue({ type: 'thinking', delta: part['text'] as string });\n break;\n case 'tool-call':\n controller.enqueue({\n type: 'tool_use',\n id: part['toolCallId'] as string,\n name: part['toolName'] as string,\n input: (part['input'] ?? {}) as Record<string, unknown>,\n });\n break;\n case 'finish':\n controller.enqueue({ type: 'done', usage: toAparteUsage(part['totalUsage'] as Parameters<typeof toAparteUsage>[0]) });\n // Terminal: stop reading `fullStream` so a stray second\n // finish/error part (if the SDK ever emits one) can't\n // enqueue past the done event.\n return;\n case 'error': {\n const err = part['error'];\n controller.enqueue({ type: 'error', message: err instanceof Error ? err.message : String(err) });\n return;\n }\n // 'abort', step markers, tool-input-* deltas, sources… → dropped.\n }\n }\n } catch (err: unknown) {\n // AbortError surfaces here when ctx.signal fires mid-stream: the\n // consumer (agent loop) cancelled on purpose — end quietly.\n if ((err as { name?: string })?.name !== 'AbortError') {\n controller.enqueue({ type: 'error', message: (err as Error | undefined)?.message ?? 'Stream error' });\n }\n } finally {\n controller.close();\n }\n },\n async cancel(reason) {\n await iterator?.return?.(reason);\n },\n });\n}\n\n// ─── The provider factory ────────────────────────────────────────────────────\n\nconst DEFAULT_CONFIG_SCHEMA = (opts: AiSdkProviderOptions): AparteAIProviderConfigSchema => ({\n fields: opts.isLocal\n ? []\n : [{ id: 'apiKey', type: 'password', label: 'API Key', required: true }],\n});\n\n/**\n * Wrap an AI SDK model factory into an `AparteAIProvider`. The returned provider\n * owns its I/O through the SDK (`chat()` shape — `AparteDirectTransport` delegates\n * to it and forwards the abort signal).\n */\nexport function createAiSdkProvider(opts: AiSdkProviderOptions): AparteAIProvider {\n const displayName = opts.name ?? opts.id;\n\n return {\n id: opts.id,\n\n getMetadata() {\n return {\n id: opts.id,\n name: displayName,\n icon: opts.icon,\n color: opts.color,\n description: opts.description,\n helpUrl: opts.helpUrl,\n hasFreeModels: opts.hasFreeModels,\n isLocal: opts.isLocal,\n configSchema: opts.configSchema ?? DEFAULT_CONFIG_SCHEMA(opts),\n };\n },\n\n getModels(): AparteAIModel[] {\n return opts.models ?? [];\n },\n\n async chat(\n request: AparteChatRequest,\n auth?: string | Record<string, string>,\n ctx?: { providerId: string; signal?: AbortSignal },\n ): Promise<AparteChatResponse> {\n const model = opts.languageModel(request.modelId ?? '', auth);\n\n const result = streamText({\n model,\n messages: toModelMessages(request.messages),\n temperature: request.temperature,\n maxOutputTokens: request.maxTokens,\n seed: request.seed,\n abortSignal: ctx?.signal,\n ...(request.tools?.length\n ? { tools: toToolSet(request.tools), toolChoice: toToolChoice(request.toolChoice) ?? 'auto' }\n : {}),\n });\n\n if (request.stream === false) {\n return await result.text;\n }\n return fullStreamToAparteEvents(result.fullStream);\n },\n };\n}\n\nexport type { AparteAIProvider, AparteAIModel } from '@aparte/core';\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aparte/provider-ai-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Bridge any Vercel AI SDK model into aparté — bring your own @ai-sdk/* package (Anthropic, Google, OpenAI, 25+ vendors) and aparté renders it.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -24,17 +24,17 @@
|
|
|
24
24
|
"node": ">=18"
|
|
25
25
|
},
|
|
26
26
|
"peerDependencies": {
|
|
27
|
-
"@aparte/core": ">=0.
|
|
27
|
+
"@aparte/core": ">=0.7.0 <1.0.0",
|
|
28
28
|
"ai": "^7.0.0"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
|
+
"@ai-sdk/anthropic": "^2.0.0",
|
|
31
32
|
"@types/node": "^22.0.0",
|
|
32
33
|
"ai": "^7.0.0",
|
|
33
34
|
"typescript": "^5.4.0",
|
|
34
35
|
"vite": "^6.0.0",
|
|
35
|
-
"vite-plugin-dts": "^4.5.4",
|
|
36
36
|
"zod": "^4.1.8",
|
|
37
|
-
"@aparte/core": "0.
|
|
37
|
+
"@aparte/core": "0.8.0"
|
|
38
38
|
},
|
|
39
39
|
"keywords": [
|
|
40
40
|
"ai",
|