@aparte/provider-ai-sdk 0.2.0-alpha.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/LICENSE +21 -0
- package/README.md +27 -0
- package/dist/index.d.ts +99 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +179 -0
- package/dist/index.js.map +1 -0
- package/package.json +66 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 aparté
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# @aparte/provider-ai-sdk
|
|
2
|
+
|
|
3
|
+
Bridge **any** [Vercel AI SDK](https://sdk.vercel.ai) model into aparté. aparté's own wire
|
|
4
|
+
concern is deliberately tiny — [`@aparte/provider-openai-compat`](../openai-compat) covers the
|
|
5
|
+
one de-facto-standard format; everything else (Anthropic, Google, Bedrock, 25+ vendors) rides
|
|
6
|
+
the AI SDK ecosystem through this bridge. You bring your `@ai-sdk/*` package, hand its model to
|
|
7
|
+
`createAiSdkProvider`, and the bridge maps `streamText`'s `fullStream` to aparté's events.
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { createAnthropic } from '@ai-sdk/anthropic';
|
|
11
|
+
import { createAiSdkProvider } from '@aparte/provider-ai-sdk';
|
|
12
|
+
import { AparteConfig } from '@aparte/core';
|
|
13
|
+
|
|
14
|
+
AparteConfig.registerAIProvider(createAiSdkProvider({
|
|
15
|
+
id: 'anthropic',
|
|
16
|
+
name: 'Anthropic',
|
|
17
|
+
models: [{ id: 'claude-sonnet-4-5', name: 'Claude Sonnet 4.5' }],
|
|
18
|
+
languageModel: (modelId, auth) =>
|
|
19
|
+
createAnthropic({ apiKey: typeof auth === 'string' ? auth : auth?.['apiKey'] })(modelId),
|
|
20
|
+
}));
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
`ai` is a **peerDependency pinned to the verified major** (`^7`) — this bridge is the only
|
|
24
|
+
aparté module touching the SDK's types. `@aparte/core` is a **peer dependency**.
|
|
25
|
+
|
|
26
|
+
> Part of the [aparté](https://github.com/apartejs/aparte) monorepo. ESM-only.
|
|
27
|
+
> See the **Providers** guide in the docs for the full usage.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @aparte/provider-ai-sdk — bridge any Vercel AI SDK model into aparté.
|
|
3
|
+
*
|
|
4
|
+
* aparté's own wire concern is deliberately tiny: `@aparte/provider-openai-compat`
|
|
5
|
+
* covers the one de-facto-standard format. EVERYTHING else (Anthropic, Google,
|
|
6
|
+
* Bedrock, 25+ vendors) rides the AI SDK ecosystem through this bridge: you
|
|
7
|
+
* bring your `@ai-sdk/*` package and hand its model to `createAiSdkProvider`;
|
|
8
|
+
* the bridge runs `streamText` (single step — aparté's agent loop owns the
|
|
9
|
+
* multi-turn) and maps `fullStream` parts to aparté's `AparteStreamEvent`s.
|
|
10
|
+
*
|
|
11
|
+
* ```ts
|
|
12
|
+
* import { createAnthropic } from '@ai-sdk/anthropic';
|
|
13
|
+
* import { createAiSdkProvider } from '@aparte/provider-ai-sdk';
|
|
14
|
+
*
|
|
15
|
+
* AparteConfig.registerAIProvider(createAiSdkProvider({
|
|
16
|
+
* id: 'anthropic',
|
|
17
|
+
* name: 'Anthropic',
|
|
18
|
+
* models: [{ id: 'claude-sonnet-4-5', name: 'Claude Sonnet 4.5' }],
|
|
19
|
+
* languageModel: (modelId, auth) =>
|
|
20
|
+
* createAnthropic({
|
|
21
|
+
* apiKey: typeof auth === 'string' ? auth : auth?.['apiKey'],
|
|
22
|
+
* headers: { 'anthropic-dangerous-direct-browser-access': 'true' }, // BYOK
|
|
23
|
+
* })(modelId),
|
|
24
|
+
* }));
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* DEPENDENCY POLICY: `ai` is a **peerDependency pinned to the verified major**
|
|
28
|
+
* (`^7`). The AI SDK moves fast (v5→v7 in about a year); this bridge is the
|
|
29
|
+
* ONLY aparté module touching its types, so a breaking major costs one small
|
|
30
|
+
* package bump — widened per-major after verification, never speculatively.
|
|
31
|
+
*
|
|
32
|
+
* The bridge is an `AparteAIProvider` with a `chat()` (it owns its I/O via the
|
|
33
|
+
* SDK — same shape as `@aparte/provider-transformers`), driven by
|
|
34
|
+
* `DirectTransport`'s delegation branch, which forwards `ctx.signal` so a user
|
|
35
|
+
* "stop" aborts the underlying vendor call.
|
|
36
|
+
*
|
|
37
|
+
* NOTE (loop contract): the bridge never sees `toolChoice: { name, input }` —
|
|
38
|
+
* the agent loop (engine `runStreamAgent` / core `_streamLoop`) executes that
|
|
39
|
+
* synthetic call itself and strips `toolChoice` before the transport call.
|
|
40
|
+
* Only `'auto' | 'none' | { name }` reach this module.
|
|
41
|
+
*/
|
|
42
|
+
import type { AparteAIProvider, AparteAIModel, AparteAIProviderConfigSchema, AparteChatRequest, AparteChatMessage, AparteStreamEvent } from '@aparte/core';
|
|
43
|
+
import type { LanguageModel, ModelMessage, ToolSet, ToolChoice } from 'ai';
|
|
44
|
+
export interface AiSdkProviderOptions {
|
|
45
|
+
/** Provider id used across aparté (key resolution, model picker, events). */
|
|
46
|
+
id: string;
|
|
47
|
+
/**
|
|
48
|
+
* Resolve the AI SDK model for a chat call. `auth` is the key/config the
|
|
49
|
+
* aparté key-resolver produced — rebuild the vendor provider per call for
|
|
50
|
+
* UI-driven BYOK, or ignore it if your factory already carries the key.
|
|
51
|
+
*/
|
|
52
|
+
languageModel: (modelId: string, auth?: string | Record<string, string>) => LanguageModel;
|
|
53
|
+
/** Display name (defaults to `id`). */
|
|
54
|
+
name?: string;
|
|
55
|
+
/** Brand icon (SVG string / data URI / icon-provider key). */
|
|
56
|
+
icon?: string;
|
|
57
|
+
/** Brand color. */
|
|
58
|
+
color?: string;
|
|
59
|
+
/** Short tag line. */
|
|
60
|
+
description?: string;
|
|
61
|
+
/** Where the user gets a key. */
|
|
62
|
+
helpUrl?: string;
|
|
63
|
+
/** Whether the vendor offers free models. */
|
|
64
|
+
hasFreeModels?: boolean;
|
|
65
|
+
/** Model runs locally (no key expected). */
|
|
66
|
+
isLocal?: boolean;
|
|
67
|
+
/** Model list — **consumer data** (the AI SDK has no model-listing API). */
|
|
68
|
+
models?: AparteAIModel[];
|
|
69
|
+
/** Override the default apiKey settings schema. */
|
|
70
|
+
configSchema?: AparteAIProviderConfigSchema;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* AparteChatMessage[] → AI SDK ModelMessage[]. Handles aparté's `tool_call` /
|
|
74
|
+
* `tool_result` envelope (assistant tool-call parts + tool-role results); the
|
|
75
|
+
* tool name for a result is recovered from the preceding envelope.
|
|
76
|
+
*/
|
|
77
|
+
export declare function toModelMessages(messages: AparteChatMessage[]): ModelMessage[];
|
|
78
|
+
/** AparteTool[] → AI SDK ToolSet — declaration only, NO `execute` (aparté's loop runs tools). */
|
|
79
|
+
export declare function toToolSet(tools: NonNullable<AparteChatRequest['tools']>): ToolSet;
|
|
80
|
+
/** aparté toolChoice → AI SDK toolChoice (the synthetic {name,input} never reaches here). */
|
|
81
|
+
export declare function toToolChoice(choice: AparteChatRequest['toolChoice']): ToolChoice<ToolSet> | undefined;
|
|
82
|
+
/**
|
|
83
|
+
* Map the AI SDK `fullStream` to aparté's event stream:
|
|
84
|
+
* `text-delta`→text · `reasoning-delta`→thinking · `tool-call`→tool_use ·
|
|
85
|
+
* `finish`→done{usage} · `error`→error. Everything else (step markers,
|
|
86
|
+
* tool-input deltas, sources, files) is dropped — aparté's loop consumes whole
|
|
87
|
+
* tool calls, not input deltas.
|
|
88
|
+
*/
|
|
89
|
+
export declare function fullStreamToAparteEvents(fullStream: AsyncIterable<{
|
|
90
|
+
type: string;
|
|
91
|
+
} & Record<string, unknown>>): ReadableStream<AparteStreamEvent>;
|
|
92
|
+
/**
|
|
93
|
+
* Wrap an AI SDK model factory into an `AparteAIProvider`. The returned provider
|
|
94
|
+
* owns its I/O through the SDK (`chat()` shape — `DirectTransport` delegates
|
|
95
|
+
* to it and forwards the abort signal).
|
|
96
|
+
*/
|
|
97
|
+
export declare function createAiSdkProvider(opts: AiSdkProviderOptions): AparteAIProvider;
|
|
98
|
+
export type { AparteAIProvider, AparteAIModel } from '@aparte/core';
|
|
99
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAEH,OAAO,KAAK,EACR,gBAAgB,EAChB,aAAa,EACb,4BAA4B,EAC5B,iBAAiB,EAEjB,iBAAiB,EACjB,iBAAiB,EAEpB,MAAM,cAAc,CAAC;AAGtB,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AAI3E,MAAM,WAAW,oBAAoB;IACjC,6EAA6E;IAC7E,EAAE,EAAE,MAAM,CAAC;IACX;;;;OAIG;IACH,aAAa,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,aAAa,CAAC;IAC1F,uCAAuC;IACvC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,8DAA8D;IAC9D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mBAAmB;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sBAAsB;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iCAAiC;IACjC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,6CAA6C;IAC7C,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,4CAA4C;IAC5C,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,4EAA4E;IAC5E,MAAM,CAAC,EAAE,aAAa,EAAE,CAAC;IACzB,mDAAmD;IACnD,YAAY,CAAC,EAAE,4BAA4B,CAAC;CAC/C;AAID;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,iBAAiB,EAAE,GAAG,YAAY,EAAE,CA6D7E;AAED,iGAAiG;AACjG,wBAAgB,SAAS,CAAC,KAAK,EAAE,WAAW,CAAC,iBAAiB,CAAC,OAAO,CAAC,CAAC,GAAG,OAAO,CAQjF;AAED,6FAA6F;AAC7F,wBAAgB,YAAY,CAAC,MAAM,EAAE,iBAAiB,CAAC,YAAY,CAAC,GAAG,UAAU,CAAC,OAAO,CAAC,GAAG,SAAS,CAIrG;AAYD;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CACpC,UAAU,EAAE,aAAa,CAAC;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GACtE,cAAc,CAAC,iBAAiB,CAAC,CA0DnC;AAUD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,oBAAoB,GAAG,gBAAgB,CAiDhF;AAED,YAAY,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { contentToText } from "@aparte/core";
|
|
2
|
+
import { tool, jsonSchema, streamText } from "ai";
|
|
3
|
+
function toModelMessages(messages) {
|
|
4
|
+
const toolNames = /* @__PURE__ */ new Map();
|
|
5
|
+
for (const msg of messages) {
|
|
6
|
+
if (msg.role === "tool_call") {
|
|
7
|
+
for (const tc of msg.toolCalls ?? []) toolNames.set(tc.id, tc.name);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
const out = [];
|
|
11
|
+
for (const msg of messages) {
|
|
12
|
+
if (msg.role === "tool_call") {
|
|
13
|
+
out.push({
|
|
14
|
+
role: "assistant",
|
|
15
|
+
content: [
|
|
16
|
+
...msg.precedingText ? [{ type: "text", text: msg.precedingText }] : [],
|
|
17
|
+
...(msg.toolCalls ?? []).map((tc) => ({
|
|
18
|
+
type: "tool-call",
|
|
19
|
+
toolCallId: tc.id,
|
|
20
|
+
toolName: tc.name,
|
|
21
|
+
input: tc.input
|
|
22
|
+
}))
|
|
23
|
+
]
|
|
24
|
+
});
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
if (msg.role === "tool_result") {
|
|
28
|
+
out.push({
|
|
29
|
+
role: "tool",
|
|
30
|
+
content: [{
|
|
31
|
+
type: "tool-result",
|
|
32
|
+
toolCallId: msg.toolCallId ?? "",
|
|
33
|
+
toolName: toolNames.get(msg.toolCallId ?? "") ?? "unknown",
|
|
34
|
+
output: { type: "text", value: contentToText(msg.content) }
|
|
35
|
+
}]
|
|
36
|
+
});
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (msg.role === "system") {
|
|
40
|
+
out.push({ role: "system", content: contentToText(msg.content) });
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (msg.role === "assistant") {
|
|
44
|
+
out.push({ role: "assistant", content: contentToText(msg.content) });
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (typeof msg.content === "string") {
|
|
48
|
+
out.push({ role: "user", content: msg.content });
|
|
49
|
+
} else {
|
|
50
|
+
out.push({
|
|
51
|
+
role: "user",
|
|
52
|
+
content: msg.content.map((p) => {
|
|
53
|
+
if (p.type === "text") return { type: "text", text: p.text };
|
|
54
|
+
if (p.type === "image") return { type: "image", image: p.image };
|
|
55
|
+
return { type: "text", text: "" };
|
|
56
|
+
})
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
62
|
+
function toToolSet(tools) {
|
|
63
|
+
return Object.fromEntries(tools.map((t) => [
|
|
64
|
+
t.name,
|
|
65
|
+
tool({
|
|
66
|
+
description: t.description,
|
|
67
|
+
inputSchema: jsonSchema(t.inputSchema)
|
|
68
|
+
})
|
|
69
|
+
]));
|
|
70
|
+
}
|
|
71
|
+
function toToolChoice(choice) {
|
|
72
|
+
if (choice === "auto" || choice === "none") return choice;
|
|
73
|
+
if (choice && typeof choice === "object") return { type: "tool", toolName: choice.name };
|
|
74
|
+
return void 0;
|
|
75
|
+
}
|
|
76
|
+
function toAparteUsage(u) {
|
|
77
|
+
return {
|
|
78
|
+
inputTokens: u.inputTokens ?? 0,
|
|
79
|
+
outputTokens: u.outputTokens ?? 0,
|
|
80
|
+
totalTokens: u.totalTokens,
|
|
81
|
+
cacheReadTokens: u.inputTokenDetails?.cacheReadTokens
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function fullStreamToAparteEvents(fullStream) {
|
|
85
|
+
let iterator;
|
|
86
|
+
return new ReadableStream({
|
|
87
|
+
async start(controller) {
|
|
88
|
+
iterator = fullStream[Symbol.asyncIterator]();
|
|
89
|
+
try {
|
|
90
|
+
while (true) {
|
|
91
|
+
const { value: part, done } = await iterator.next();
|
|
92
|
+
if (done) break;
|
|
93
|
+
switch (part.type) {
|
|
94
|
+
case "text-delta":
|
|
95
|
+
controller.enqueue({ type: "text", delta: part["text"] });
|
|
96
|
+
break;
|
|
97
|
+
case "reasoning-delta":
|
|
98
|
+
controller.enqueue({ type: "thinking", delta: part["text"] });
|
|
99
|
+
break;
|
|
100
|
+
case "tool-call":
|
|
101
|
+
controller.enqueue({
|
|
102
|
+
type: "tool_use",
|
|
103
|
+
id: part["toolCallId"],
|
|
104
|
+
name: part["toolName"],
|
|
105
|
+
input: part["input"] ?? {}
|
|
106
|
+
});
|
|
107
|
+
break;
|
|
108
|
+
case "finish":
|
|
109
|
+
controller.enqueue({ type: "done", usage: toAparteUsage(part["totalUsage"]) });
|
|
110
|
+
return;
|
|
111
|
+
case "error": {
|
|
112
|
+
const err = part["error"];
|
|
113
|
+
controller.enqueue({ type: "error", message: err instanceof Error ? err.message : String(err) });
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
} catch (err) {
|
|
119
|
+
if (err?.name !== "AbortError") {
|
|
120
|
+
controller.enqueue({ type: "error", message: err?.message ?? "Stream error" });
|
|
121
|
+
}
|
|
122
|
+
} finally {
|
|
123
|
+
controller.close();
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
async cancel(reason) {
|
|
127
|
+
await iterator?.return?.(reason);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
const DEFAULT_CONFIG_SCHEMA = (opts) => ({
|
|
132
|
+
fields: opts.isLocal ? [] : [{ id: "apiKey", type: "password", label: "API Key", required: true }]
|
|
133
|
+
});
|
|
134
|
+
function createAiSdkProvider(opts) {
|
|
135
|
+
const displayName = opts.name ?? opts.id;
|
|
136
|
+
return {
|
|
137
|
+
id: opts.id,
|
|
138
|
+
getMetadata() {
|
|
139
|
+
return {
|
|
140
|
+
id: opts.id,
|
|
141
|
+
name: displayName,
|
|
142
|
+
icon: opts.icon,
|
|
143
|
+
color: opts.color,
|
|
144
|
+
description: opts.description,
|
|
145
|
+
helpUrl: opts.helpUrl,
|
|
146
|
+
hasFreeModels: opts.hasFreeModels,
|
|
147
|
+
isLocal: opts.isLocal,
|
|
148
|
+
configSchema: opts.configSchema ?? DEFAULT_CONFIG_SCHEMA(opts)
|
|
149
|
+
};
|
|
150
|
+
},
|
|
151
|
+
getModels() {
|
|
152
|
+
return opts.models ?? [];
|
|
153
|
+
},
|
|
154
|
+
async chat(request, auth, ctx) {
|
|
155
|
+
const model = opts.languageModel(request.modelId ?? "", auth);
|
|
156
|
+
const result = streamText({
|
|
157
|
+
model,
|
|
158
|
+
messages: toModelMessages(request.messages),
|
|
159
|
+
temperature: request.temperature,
|
|
160
|
+
maxOutputTokens: request.maxTokens,
|
|
161
|
+
seed: request.seed,
|
|
162
|
+
abortSignal: ctx?.signal,
|
|
163
|
+
...request.tools?.length ? { tools: toToolSet(request.tools), toolChoice: toToolChoice(request.toolChoice) ?? "auto" } : {}
|
|
164
|
+
});
|
|
165
|
+
if (request.stream === false) {
|
|
166
|
+
return await result.text;
|
|
167
|
+
}
|
|
168
|
+
return fullStreamToAparteEvents(result.fullStream);
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
export {
|
|
173
|
+
createAiSdkProvider,
|
|
174
|
+
fullStreamToAparteEvents,
|
|
175
|
+
toModelMessages,
|
|
176
|
+
toToolChoice,
|
|
177
|
+
toToolSet
|
|
178
|
+
};
|
|
179
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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":";;AA8FO,SAAS,gBAAgB,UAA+C;AAE3E,QAAM,gCAAgB,IAAA;AACtB,aAAW,OAAO,UAAU;AACxB,QAAI,IAAI,SAAS,aAAa;AAC1B,iBAAW,MAAM,IAAI,aAAa,CAAA,aAAc,IAAI,GAAG,IAAI,GAAG,IAAI;AAAA,IACtE;AAAA,EACJ;AAEA,QAAM,MAAsB,CAAA;AAC5B,aAAW,OAAO,UAAU;AACxB,QAAI,IAAI,SAAS,aAAa;AAC1B,UAAI,KAAK;AAAA,QACL,MAAM;AAAA,QACN,SAAS;AAAA,UACL,GAAI,IAAI,gBAAgB,CAAC,EAAE,MAAM,QAAiB,MAAM,IAAI,cAAA,CAAe,IAAI,CAAA;AAAA,UAC/E,IAAI,IAAI,aAAa,CAAA,GAAI,IAAI,CAAA,QAAO;AAAA,YAChC,MAAM;AAAA,YACN,YAAY,GAAG;AAAA,YACf,UAAU,GAAG;AAAA,YACb,OAAO,GAAG;AAAA,UAAA,EACZ;AAAA,QAAA;AAAA,MACN,CACH;AACD;AAAA,IACJ;AACA,QAAI,IAAI,SAAS,eAAe;AAC5B,UAAI,KAAK;AAAA,QACL,MAAM;AAAA,QACN,SAAS,CAAC;AAAA,UACN,MAAM;AAAA,UACN,YAAY,IAAI,cAAc;AAAA,UAC9B,UAAU,UAAU,IAAI,IAAI,cAAc,EAAE,KAAK;AAAA,UACjD,QAAQ,EAAE,MAAM,QAAQ,OAAO,cAAc,IAAI,OAAO,EAAA;AAAA,QAAE,CAC7D;AAAA,MAAA,CACJ;AACD;AAAA,IACJ;AACA,QAAI,IAAI,SAAS,UAAU;AACvB,UAAI,KAAK,EAAE,MAAM,UAAU,SAAS,cAAc,IAAI,OAAO,GAAG;AAChE;AAAA,IACJ;AACA,QAAI,IAAI,SAAS,aAAa;AAC1B,UAAI,KAAK,EAAE,MAAM,aAAa,SAAS,cAAc,IAAI,OAAO,GAAG;AACnE;AAAA,IACJ;AAEA,QAAI,OAAO,IAAI,YAAY,UAAU;AACjC,UAAI,KAAK,EAAE,MAAM,QAAQ,SAAS,IAAI,SAAS;AAAA,IACnD,OAAO;AACH,UAAI,KAAK;AAAA,QACL,MAAM;AAAA,QACN,SAAS,IAAI,QAAQ,IAAI,CAAA,MAAK;AAC1B,cAAI,EAAE,SAAS,OAAQ,QAAO,EAAE,MAAM,QAAiB,MAAM,EAAE,KAAA;AAC/D,cAAI,EAAE,SAAS,QAAS,QAAO,EAAE,MAAM,SAAkB,OAAO,EAAE,MAAA;AAClE,iBAAO,EAAE,MAAM,QAAiB,MAAM,GAAA;AAAA,QAC1C,CAAC;AAAA,MAAA,CACJ;AAAA,IACL;AAAA,EACJ;AACA,SAAO;AACX;AAGO,SAAS,UAAU,OAAyD;AAC/E,SAAO,OAAO,YAAY,MAAM,IAAI,CAAA,MAAK;AAAA,IACrC,EAAE;AAAA,IACF,KAAK;AAAA,MACD,aAAa,EAAE;AAAA,MACf,aAAa,WAAW,EAAE,WAA+C;AAAA,IAAA,CAC5E;AAAA,EAAA,CACJ,CAAC;AACN;AAGO,SAAS,aAAa,QAA0E;AACnG,MAAI,WAAW,UAAU,WAAW,OAAQ,QAAO;AACnD,MAAI,UAAU,OAAO,WAAW,SAAU,QAAO,EAAE,MAAM,QAAQ,UAAU,OAAO,KAAA;AAClF,SAAO;AACX;AAGA,SAAS,cAAc,GAAyI;AAC5J,SAAO;AAAA,IACH,aAAa,EAAE,eAAe;AAAA,IAC9B,cAAc,EAAE,gBAAgB;AAAA,IAChC,aAAa,EAAE;AAAA,IACf,iBAAiB,EAAE,mBAAmB;AAAA,EAAA;AAE9C;AASO,SAAS,yBACZ,YACiC;AAKjC,MAAI;AAEJ,SAAO,IAAI,eAAkC;AAAA,IACzC,MAAM,MAAM,YAAY;AACpB,iBAAW,WAAW,OAAO,aAAa,EAAA;AAC1C,UAAI;AACA,eAAO,MAAM;AACT,gBAAM,EAAE,OAAO,MAAM,SAAS,MAAM,SAAS,KAAA;AAC7C,cAAI,KAAM;AAEV,kBAAQ,KAAK,MAAA;AAAA,YACT,KAAK;AACD,yBAAW,QAAQ,EAAE,MAAM,QAAQ,OAAO,KAAK,MAAM,GAAa;AAClE;AAAA,YACJ,KAAK;AACD,yBAAW,QAAQ,EAAE,MAAM,YAAY,OAAO,KAAK,MAAM,GAAa;AACtE;AAAA,YACJ,KAAK;AACD,yBAAW,QAAQ;AAAA,gBACf,MAAM;AAAA,gBACN,IAAI,KAAK,YAAY;AAAA,gBACrB,MAAM,KAAK,UAAU;AAAA,gBACrB,OAAQ,KAAK,OAAO,KAAK,CAAA;AAAA,cAAC,CAC7B;AACD;AAAA,YACJ,KAAK;AACD,yBAAW,QAAQ,EAAE,MAAM,QAAQ,OAAO,cAAc,KAAK,YAAY,CAAwC,GAAG;AAIpH;AAAA,YACJ,KAAK,SAAS;AACV,oBAAM,MAAM,KAAK,OAAO;AACxB,yBAAW,QAAQ,EAAE,MAAM,SAAS,SAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAA,CAAG;AAC/F;AAAA,YACJ;AAAA,UAAA;AAAA,QAGR;AAAA,MACJ,SAAS,KAAc;AAGnB,YAAK,KAA2B,SAAS,cAAc;AACnD,qBAAW,QAAQ,EAAE,MAAM,SAAS,SAAU,KAA2B,WAAW,gBAAgB;AAAA,QACxG;AAAA,MACJ,UAAA;AACI,mBAAW,MAAA;AAAA,MACf;AAAA,IACJ;AAAA,IACA,MAAM,OAAO,QAAQ;AACjB,YAAM,UAAU,SAAS,MAAM;AAAA,IACnC;AAAA,EAAA,CACH;AACL;AAIA,MAAM,wBAAwB,CAAC,UAA8D;AAAA,EACzF,QAAQ,KAAK,UACP,CAAA,IACA,CAAC,EAAE,IAAI,UAAU,MAAM,YAAY,OAAO,WAAW,UAAU,MAAM;AAC/E;AAOO,SAAS,oBAAoB,MAA8C;AAC9E,QAAM,cAAc,KAAK,QAAQ,KAAK;AAEtC,SAAO;AAAA,IACH,IAAI,KAAK;AAAA,IAET,cAAc;AACV,aAAO;AAAA,QACH,IAAI,KAAK;AAAA,QACT,MAAM;AAAA,QACN,MAAM,KAAK;AAAA,QACX,OAAO,KAAK;AAAA,QACZ,aAAa,KAAK;AAAA,QAClB,SAAS,KAAK;AAAA,QACd,eAAe,KAAK;AAAA,QACpB,SAAS,KAAK;AAAA,QACd,cAAc,KAAK,gBAAgB,sBAAsB,IAAI;AAAA,MAAA;AAAA,IAErE;AAAA,IAEA,YAA6B;AACzB,aAAO,KAAK,UAAU,CAAA;AAAA,IAC1B;AAAA,IAEA,MAAM,KACF,SACA,MACA,KAC2B;AAC3B,YAAM,QAAQ,KAAK,cAAc,QAAQ,WAAW,IAAI,IAAI;AAE5D,YAAM,SAAS,WAAW;AAAA,QACtB;AAAA,QACA,UAAU,gBAAgB,QAAQ,QAAQ;AAAA,QAC1C,aAAa,QAAQ;AAAA,QACrB,iBAAiB,QAAQ;AAAA,QACzB,MAAM,QAAQ;AAAA,QACd,aAAa,KAAK;AAAA,QAClB,GAAI,QAAQ,OAAO,SACb,EAAE,OAAO,UAAU,QAAQ,KAAK,GAAG,YAAY,aAAa,QAAQ,UAAU,KAAK,OAAA,IACnF,CAAA;AAAA,MAAC,CACV;AAED,UAAI,QAAQ,WAAW,OAAO;AAC1B,eAAO,MAAM,OAAO;AAAA,MACxB;AACA,aAAO,yBAAyB,OAAO,UAAU;AAAA,IACrD;AAAA,EAAA;AAER;"}
|
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@aparte/provider-ai-sdk",
|
|
3
|
+
"version": "0.2.0-alpha.0",
|
|
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
|
+
"type": "module",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"module": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"@aparte-workspace/source": "./src/index.ts",
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"import": "./dist/index.js",
|
|
15
|
+
"default": "./dist/index.js"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"README.md",
|
|
21
|
+
"LICENSE"
|
|
22
|
+
],
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=18"
|
|
25
|
+
},
|
|
26
|
+
"peerDependencies": {
|
|
27
|
+
"ai": "^7.0.0",
|
|
28
|
+
"@aparte/core": "0.2.0-alpha.0"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@types/node": "^22.0.0",
|
|
32
|
+
"ai": "^7.0.0",
|
|
33
|
+
"typescript": "^5.4.0",
|
|
34
|
+
"vite": "^6.0.0",
|
|
35
|
+
"vite-plugin-dts": "^4.5.4",
|
|
36
|
+
"zod": "^4.1.8",
|
|
37
|
+
"@aparte/core": "0.2.0-alpha.0"
|
|
38
|
+
},
|
|
39
|
+
"keywords": [
|
|
40
|
+
"ai",
|
|
41
|
+
"provider",
|
|
42
|
+
"vercel-ai-sdk",
|
|
43
|
+
"ai-sdk",
|
|
44
|
+
"anthropic",
|
|
45
|
+
"google",
|
|
46
|
+
"llm",
|
|
47
|
+
"bridge"
|
|
48
|
+
],
|
|
49
|
+
"license": "MIT",
|
|
50
|
+
"repository": {
|
|
51
|
+
"type": "git",
|
|
52
|
+
"url": "git+https://github.com/apartejs/aparte.git",
|
|
53
|
+
"directory": "packages/providers/ai/ai-sdk"
|
|
54
|
+
},
|
|
55
|
+
"bugs": {
|
|
56
|
+
"url": "https://github.com/apartejs/aparte/issues"
|
|
57
|
+
},
|
|
58
|
+
"scripts": {
|
|
59
|
+
"dev": "vite",
|
|
60
|
+
"build": "vite build && tsc -b --emitDeclarationOnly --force",
|
|
61
|
+
"preview": "vite preview",
|
|
62
|
+
"test": "vitest",
|
|
63
|
+
"test:run": "vitest run",
|
|
64
|
+
"test:coverage": "vitest run --coverage"
|
|
65
|
+
}
|
|
66
|
+
}
|