@idemcore/ai-sdk-provider 0.0.0-stage → 0.1.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 +78 -2
- package/dist/convert.d.ts +18 -0
- package/dist/convert.js +171 -0
- package/dist/errors.d.ts +10 -0
- package/dist/errors.js +14 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +5 -0
- package/dist/language-model.d.ts +21 -0
- package/dist/language-model.js +113 -0
- package/dist/provider.d.ts +17 -0
- package/dist/provider.js +24 -0
- package/dist/receipt.d.ts +13 -0
- package/dist/receipt.js +16 -0
- package/package.json +46 -4
package/README.md
CHANGED
|
@@ -1,3 +1,79 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @idemcore/ai-sdk-provider
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Vercel AI SDK provider for the [Idemcore](https://idemcore.net) API. Every
|
|
4
|
+
answer comes with a signed, independently verifiable, bit-exact receipt,
|
|
5
|
+
exposed on `providerMetadata.idemcore`.
|
|
6
|
+
|
|
7
|
+
This package implements the current `LanguageModelV4` / `ProviderV4`
|
|
8
|
+
specification used by the `ai` package v7 (`@ai-sdk/provider` v4).
|
|
9
|
+
|
|
10
|
+
Private package. Do not publish to npm without explicit approval.
|
|
11
|
+
|
|
12
|
+
## Install (local)
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
# from this directory
|
|
16
|
+
npm install
|
|
17
|
+
export IDEMCORE_API_KEY=ick_...
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Peer: `ai` >= 7. Depends on `@idemcore/sdk` 0.3.0 (r2 receipts) and
|
|
21
|
+
`@ai-sdk/provider` ^4.
|
|
22
|
+
|
|
23
|
+
## Quickstart
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import { generateText, streamText } from "ai";
|
|
27
|
+
import { createIdemcore } from "@idemcore/ai-sdk-provider";
|
|
28
|
+
|
|
29
|
+
const idemcore = createIdemcore(); // reads IDEMCORE_API_KEY
|
|
30
|
+
const { text, providerMetadata } = await generateText({
|
|
31
|
+
model: idemcore("your-model-id"),
|
|
32
|
+
prompt: "Name the three primary colours.",
|
|
33
|
+
maxOutputTokens: 64,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
console.log(text);
|
|
37
|
+
console.log(providerMetadata?.idemcore?.receiptId); // rcpt_...
|
|
38
|
+
console.log(providerMetadata?.idemcore?.receipt); // full signed receipt
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Optional privacy (passed through as `providerOptions`):
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
await generateText({
|
|
45
|
+
model: idemcore("your-model-id"),
|
|
46
|
+
prompt: "Hello",
|
|
47
|
+
providerOptions: { idemcore: { privacy: "hash_only" } },
|
|
48
|
+
});
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Streaming yields text deltas. The receipt arrives on the final `finish` part
|
|
52
|
+
via `providerMetadata.idemcore`, once the answer is complete.
|
|
53
|
+
|
|
54
|
+
You can re-check a receipt locally with `verifyReceipt` (re-exported from
|
|
55
|
+
`@idemcore/sdk`).
|
|
56
|
+
|
|
57
|
+
## Sampling
|
|
58
|
+
|
|
59
|
+
Idemcore decoding is greedy: **temperature is fixed at 0**. A non-zero
|
|
60
|
+
temperature throws before a request is made. Other sampling settings
|
|
61
|
+
(`topP`, `topK`, penalties, `seed`, `stopSequences`) and tool calling produce
|
|
62
|
+
`unsupported` warnings (or an error if `toolChoice` requires a tool).
|
|
63
|
+
`maxOutputTokens` is forwarded as the SDK's `maxTokens` (1–2048).
|
|
64
|
+
|
|
65
|
+
Auth is `IDEMCORE_API_KEY` (or `createIdemcore({ apiKey })`). Billing is
|
|
66
|
+
measured server-side in tokens.
|
|
67
|
+
|
|
68
|
+
Embeddings, image, and speech models are not available; those provider
|
|
69
|
+
methods throw `NoSuchModelError`.
|
|
70
|
+
|
|
71
|
+
## Tests
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
npm test
|
|
75
|
+
npm run build
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Unit tests mock the SDK. A live smoke test runs only when `IDEMCORE_API_KEY`
|
|
79
|
+
is set in the environment.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { LanguageModelV4CallOptions, LanguageModelV4FinishReason, LanguageModelV4Prompt, LanguageModelV4Usage, SharedV4Warning } from "@ai-sdk/provider";
|
|
2
|
+
import type { ChatOptions, Message } from "@idemcore/sdk";
|
|
3
|
+
export type PreparedCall = {
|
|
4
|
+
messages: Message[];
|
|
5
|
+
options: ChatOptions;
|
|
6
|
+
warnings: SharedV4Warning[];
|
|
7
|
+
};
|
|
8
|
+
export declare function convertPrompt(prompt: LanguageModelV4Prompt): Message[];
|
|
9
|
+
export declare function mapFinishReason(raw: string | null | undefined): LanguageModelV4FinishReason;
|
|
10
|
+
export declare function mapUsage(usage: {
|
|
11
|
+
prompt_tokens?: number;
|
|
12
|
+
completion_tokens?: number;
|
|
13
|
+
total_tokens?: number;
|
|
14
|
+
} | null): LanguageModelV4Usage;
|
|
15
|
+
export declare function prepareCall(call: LanguageModelV4CallOptions, defaults: {
|
|
16
|
+
model: string;
|
|
17
|
+
privacy?: "hash_only" | "full";
|
|
18
|
+
}): PreparedCall;
|
package/dist/convert.js
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { IdemcoreProviderError } from "./errors.js";
|
|
2
|
+
function textFromParts(parts, role) {
|
|
3
|
+
const texts = [];
|
|
4
|
+
for (const part of parts) {
|
|
5
|
+
if (part.type === "text" && typeof part.text === "string") {
|
|
6
|
+
texts.push(part.text);
|
|
7
|
+
continue;
|
|
8
|
+
}
|
|
9
|
+
if (part.type === "file") {
|
|
10
|
+
throw new IdemcoreProviderError("Idemcore chat accepts text messages only; file or image parts are not supported.");
|
|
11
|
+
}
|
|
12
|
+
if (part.type === "tool-call" || part.type === "tool-result") {
|
|
13
|
+
throw new IdemcoreProviderError("Idemcore chat does not support tool calling.");
|
|
14
|
+
}
|
|
15
|
+
if (part.type === "reasoning") {
|
|
16
|
+
// Reasoning parts are not sent to the text-only API.
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
throw new IdemcoreProviderError(`Unsupported ${role} content part type: ${String(part.type)}.`);
|
|
20
|
+
}
|
|
21
|
+
return texts.join("");
|
|
22
|
+
}
|
|
23
|
+
export function convertPrompt(prompt) {
|
|
24
|
+
const messages = [];
|
|
25
|
+
for (const msg of prompt) {
|
|
26
|
+
if (msg.role === "system") {
|
|
27
|
+
messages.push({ role: "system", content: msg.content });
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if (msg.role === "user") {
|
|
31
|
+
messages.push({ role: "user", content: textFromParts(msg.content, "user") });
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (msg.role === "assistant") {
|
|
35
|
+
messages.push({
|
|
36
|
+
role: "assistant",
|
|
37
|
+
content: textFromParts(msg.content, "assistant"),
|
|
38
|
+
});
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (msg.role === "tool") {
|
|
42
|
+
throw new IdemcoreProviderError("Idemcore chat does not accept tool messages.");
|
|
43
|
+
}
|
|
44
|
+
throw new IdemcoreProviderError(`Unsupported message role: ${msg.role}.`);
|
|
45
|
+
}
|
|
46
|
+
if (messages.length < 1 || messages.length > 64) {
|
|
47
|
+
throw new IdemcoreProviderError("Idemcore chat requires between 1 and 64 messages.");
|
|
48
|
+
}
|
|
49
|
+
return messages;
|
|
50
|
+
}
|
|
51
|
+
export function mapFinishReason(raw) {
|
|
52
|
+
if (raw === "stop")
|
|
53
|
+
return { unified: "stop", raw: raw };
|
|
54
|
+
if (raw === "length")
|
|
55
|
+
return { unified: "length", raw: raw };
|
|
56
|
+
if (raw === "content_filter" || raw === "content-filter") {
|
|
57
|
+
return { unified: "content-filter", raw: raw };
|
|
58
|
+
}
|
|
59
|
+
if (!raw)
|
|
60
|
+
return { unified: "other", raw: undefined };
|
|
61
|
+
return { unified: "other", raw };
|
|
62
|
+
}
|
|
63
|
+
export function mapUsage(usage) {
|
|
64
|
+
const input = usage?.prompt_tokens;
|
|
65
|
+
const output = usage?.completion_tokens;
|
|
66
|
+
return {
|
|
67
|
+
inputTokens: {
|
|
68
|
+
total: input,
|
|
69
|
+
noCache: undefined,
|
|
70
|
+
cacheRead: undefined,
|
|
71
|
+
cacheWrite: undefined,
|
|
72
|
+
},
|
|
73
|
+
outputTokens: {
|
|
74
|
+
total: output,
|
|
75
|
+
text: output,
|
|
76
|
+
reasoning: undefined,
|
|
77
|
+
},
|
|
78
|
+
raw: usage ? { ...usage } : undefined,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
export function prepareCall(call, defaults) {
|
|
82
|
+
const warnings = [];
|
|
83
|
+
if (call.temperature !== undefined && call.temperature !== 0) {
|
|
84
|
+
throw new IdemcoreProviderError("Idemcore uses greedy decoding: temperature is fixed at 0.");
|
|
85
|
+
}
|
|
86
|
+
if (call.topP !== undefined && call.topP !== 1) {
|
|
87
|
+
warnings.push({
|
|
88
|
+
type: "unsupported",
|
|
89
|
+
feature: "topP",
|
|
90
|
+
details: "Idemcore decoding is greedy; topP is ignored.",
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
if (call.topK !== undefined) {
|
|
94
|
+
warnings.push({
|
|
95
|
+
type: "unsupported",
|
|
96
|
+
feature: "topK",
|
|
97
|
+
details: "Idemcore decoding is greedy; topK is ignored.",
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
if (call.presencePenalty) {
|
|
101
|
+
warnings.push({
|
|
102
|
+
type: "unsupported",
|
|
103
|
+
feature: "presencePenalty",
|
|
104
|
+
details: "Idemcore decoding is greedy; presencePenalty is ignored.",
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
if (call.frequencyPenalty) {
|
|
108
|
+
warnings.push({
|
|
109
|
+
type: "unsupported",
|
|
110
|
+
feature: "frequencyPenalty",
|
|
111
|
+
details: "Idemcore decoding is greedy; frequencyPenalty is ignored.",
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
if (call.seed !== undefined) {
|
|
115
|
+
warnings.push({
|
|
116
|
+
type: "unsupported",
|
|
117
|
+
feature: "seed",
|
|
118
|
+
details: "The public Idemcore SDK chat API does not accept a seed.",
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
if (call.stopSequences && call.stopSequences.length > 0) {
|
|
122
|
+
warnings.push({
|
|
123
|
+
type: "unsupported",
|
|
124
|
+
feature: "stopSequences",
|
|
125
|
+
details: "Idemcore does not support stop sequences; they would not be reflected in the signed receipt.",
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
if (call.responseFormat?.type === "json") {
|
|
129
|
+
warnings.push({
|
|
130
|
+
type: "unsupported",
|
|
131
|
+
feature: "responseFormat",
|
|
132
|
+
details: "Idemcore chat returns plain text; JSON responseFormat is ignored.",
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
if (call.reasoning && call.reasoning !== "provider-default" && call.reasoning !== "none") {
|
|
136
|
+
warnings.push({
|
|
137
|
+
type: "unsupported",
|
|
138
|
+
feature: "reasoning",
|
|
139
|
+
details: "Idemcore chat does not expose a separate reasoning setting.",
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
if (call.tools && call.tools.length > 0) {
|
|
143
|
+
if (call.toolChoice && call.toolChoice.type !== "none" && call.toolChoice.type !== "auto") {
|
|
144
|
+
throw new IdemcoreProviderError("Idemcore chat does not support tool calling, so a required toolChoice cannot be honored.");
|
|
145
|
+
}
|
|
146
|
+
warnings.push({
|
|
147
|
+
type: "unsupported",
|
|
148
|
+
feature: "tools",
|
|
149
|
+
details: "Idemcore chat accepts text messages only; tool calling is not available.",
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
else if (call.toolChoice && call.toolChoice.type !== "none" && call.toolChoice.type !== "auto") {
|
|
153
|
+
throw new IdemcoreProviderError("Idemcore chat does not support tool calling, so a required toolChoice cannot be honored.");
|
|
154
|
+
}
|
|
155
|
+
const provider = (call.providerOptions?.idemcore ?? {});
|
|
156
|
+
const privacy = provider.privacy ?? defaults.privacy;
|
|
157
|
+
const options = { model: defaults.model };
|
|
158
|
+
if (call.maxOutputTokens !== undefined) {
|
|
159
|
+
options.maxTokens = call.maxOutputTokens;
|
|
160
|
+
}
|
|
161
|
+
if (privacy !== undefined) {
|
|
162
|
+
options.privacy = privacy;
|
|
163
|
+
}
|
|
164
|
+
if (call.temperature === 0) {
|
|
165
|
+
options.temperature = 0;
|
|
166
|
+
}
|
|
167
|
+
if (call.abortSignal) {
|
|
168
|
+
options.signal = call.abortSignal;
|
|
169
|
+
}
|
|
170
|
+
return { messages: convertPrompt(call.prompt), options, warnings };
|
|
171
|
+
}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Errors raised when a request cannot be sent to the Idemcore chat API. */
|
|
2
|
+
export declare class IdemcoreProviderError extends Error {
|
|
3
|
+
readonly status?: number;
|
|
4
|
+
readonly type: string;
|
|
5
|
+
constructor(message: string, opts?: {
|
|
6
|
+
type?: string;
|
|
7
|
+
status?: number;
|
|
8
|
+
cause?: unknown;
|
|
9
|
+
});
|
|
10
|
+
}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Errors raised when a request cannot be sent to the Idemcore chat API. */
|
|
2
|
+
export class IdemcoreProviderError extends Error {
|
|
3
|
+
status;
|
|
4
|
+
type;
|
|
5
|
+
constructor(message, opts) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = "IdemcoreProviderError";
|
|
8
|
+
this.type = opts?.type ?? "invalid_request_error";
|
|
9
|
+
this.status = opts?.status;
|
|
10
|
+
if (opts?.cause !== undefined) {
|
|
11
|
+
this.cause = opts.cause;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { createIdemcore, idemcore } from "./provider.js";
|
|
2
|
+
export type { IdemcoreProvider, IdemcoreProviderSettings } from "./provider.js";
|
|
3
|
+
export { IdemcoreLanguageModel } from "./language-model.js";
|
|
4
|
+
export type { IdemcoreLanguageModelConfig } from "./language-model.js";
|
|
5
|
+
export { IdemcoreProviderError } from "./errors.js";
|
|
6
|
+
export { receiptMetadata, providerMetadata } from "./receipt.js";
|
|
7
|
+
export type { IdemcoreReceiptMetadata } from "./receipt.js";
|
|
8
|
+
export { verifyReceipt } from "@idemcore/sdk";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { createIdemcore, idemcore } from "./provider.js";
|
|
2
|
+
export { IdemcoreLanguageModel } from "./language-model.js";
|
|
3
|
+
export { IdemcoreProviderError } from "./errors.js";
|
|
4
|
+
export { receiptMetadata, providerMetadata } from "./receipt.js";
|
|
5
|
+
export { verifyReceipt } from "@idemcore/sdk";
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { LanguageModelV4, LanguageModelV4CallOptions, LanguageModelV4GenerateResult, LanguageModelV4StreamResult } from "@ai-sdk/provider";
|
|
2
|
+
import { Idemcore, type ChatResult, type ClientOptions } from "@idemcore/sdk";
|
|
3
|
+
export type IdemcoreLanguageModelConfig = ClientOptions & {
|
|
4
|
+
/** Default receipt privacy for this model (`hash_only` or `full`). */
|
|
5
|
+
privacy?: "hash_only" | "full";
|
|
6
|
+
/** Injected client (tests). */
|
|
7
|
+
client?: Idemcore;
|
|
8
|
+
};
|
|
9
|
+
export declare class IdemcoreLanguageModel implements LanguageModelV4 {
|
|
10
|
+
readonly specificationVersion: "v4";
|
|
11
|
+
readonly provider = "idemcore";
|
|
12
|
+
readonly modelId: string;
|
|
13
|
+
readonly supportedUrls: Record<string, RegExp[]>;
|
|
14
|
+
private readonly config;
|
|
15
|
+
private clientInstance?;
|
|
16
|
+
constructor(modelId: string, config?: IdemcoreLanguageModelConfig);
|
|
17
|
+
private client;
|
|
18
|
+
doGenerate(options: LanguageModelV4CallOptions): Promise<LanguageModelV4GenerateResult>;
|
|
19
|
+
doStream(options: LanguageModelV4CallOptions): Promise<LanguageModelV4StreamResult>;
|
|
20
|
+
}
|
|
21
|
+
export declare function toGenerateResult(result: ChatResult, warnings?: LanguageModelV4GenerateResult["warnings"]): LanguageModelV4GenerateResult;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { Idemcore } from "@idemcore/sdk";
|
|
2
|
+
import { prepareCall } from "./convert.js";
|
|
3
|
+
import { mapFinishReason, mapUsage } from "./convert.js";
|
|
4
|
+
import { IdemcoreProviderError } from "./errors.js";
|
|
5
|
+
import { providerMetadata } from "./receipt.js";
|
|
6
|
+
export class IdemcoreLanguageModel {
|
|
7
|
+
specificationVersion = "v4";
|
|
8
|
+
provider = "idemcore";
|
|
9
|
+
modelId;
|
|
10
|
+
supportedUrls = {};
|
|
11
|
+
config;
|
|
12
|
+
clientInstance;
|
|
13
|
+
constructor(modelId, config = {}) {
|
|
14
|
+
this.modelId = modelId;
|
|
15
|
+
this.config = config;
|
|
16
|
+
this.clientInstance = config.client;
|
|
17
|
+
}
|
|
18
|
+
client() {
|
|
19
|
+
if (!this.clientInstance) {
|
|
20
|
+
this.clientInstance = new Idemcore({
|
|
21
|
+
apiKey: this.config.apiKey,
|
|
22
|
+
baseURL: this.config.baseURL,
|
|
23
|
+
timeout: this.config.timeout,
|
|
24
|
+
maxRetries: this.config.maxRetries,
|
|
25
|
+
headers: this.config.headers,
|
|
26
|
+
fetch: this.config.fetch,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
return this.clientInstance;
|
|
30
|
+
}
|
|
31
|
+
async doGenerate(options) {
|
|
32
|
+
const prepared = prepareCall(options, {
|
|
33
|
+
model: this.modelId,
|
|
34
|
+
privacy: this.config.privacy,
|
|
35
|
+
});
|
|
36
|
+
const result = await this.client().chat(prepared.messages, prepared.options);
|
|
37
|
+
return toGenerateResult(result, prepared.warnings);
|
|
38
|
+
}
|
|
39
|
+
async doStream(options) {
|
|
40
|
+
const prepared = prepareCall(options, {
|
|
41
|
+
model: this.modelId,
|
|
42
|
+
privacy: this.config.privacy,
|
|
43
|
+
});
|
|
44
|
+
const stream = this.client().chatStream(prepared.messages, prepared.options);
|
|
45
|
+
const includeRaw = options.includeRawChunks === true;
|
|
46
|
+
const textId = "idemcore-text";
|
|
47
|
+
const output = new ReadableStream({
|
|
48
|
+
async start(controller) {
|
|
49
|
+
controller.enqueue({ type: "stream-start", warnings: prepared.warnings });
|
|
50
|
+
let started = false;
|
|
51
|
+
try {
|
|
52
|
+
for await (const chunk of stream) {
|
|
53
|
+
if (includeRaw) {
|
|
54
|
+
controller.enqueue({ type: "raw", rawValue: chunk.raw });
|
|
55
|
+
}
|
|
56
|
+
if (!chunk.delta) {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (!started) {
|
|
60
|
+
controller.enqueue({ type: "text-start", id: textId });
|
|
61
|
+
started = true;
|
|
62
|
+
}
|
|
63
|
+
controller.enqueue({ type: "text-delta", id: textId, delta: chunk.delta });
|
|
64
|
+
}
|
|
65
|
+
const result = await stream.finalResult();
|
|
66
|
+
if (started) {
|
|
67
|
+
controller.enqueue({ type: "text-end", id: textId });
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
controller.enqueue({ type: "text-start", id: textId });
|
|
71
|
+
controller.enqueue({ type: "text-end", id: textId });
|
|
72
|
+
}
|
|
73
|
+
controller.enqueue({
|
|
74
|
+
type: "response-metadata",
|
|
75
|
+
id: result.id || undefined,
|
|
76
|
+
modelId: result.model || undefined,
|
|
77
|
+
});
|
|
78
|
+
controller.enqueue({
|
|
79
|
+
type: "finish",
|
|
80
|
+
finishReason: mapFinishReason(result.finishReason),
|
|
81
|
+
usage: mapUsage(result.usage),
|
|
82
|
+
providerMetadata: providerMetadata(result),
|
|
83
|
+
});
|
|
84
|
+
controller.close();
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
controller.enqueue({ type: "error", error });
|
|
88
|
+
controller.close();
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
cancel() {
|
|
92
|
+
// The SDK stream is consumed in start(); abort is passed via ChatOptions.signal.
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
return { stream: output };
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
export function toGenerateResult(result, warnings = []) {
|
|
99
|
+
if (!result.receiptId) {
|
|
100
|
+
throw new IdemcoreProviderError("Idemcore response did not include a receipt id.");
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
content: [{ type: "text", text: result.text }],
|
|
104
|
+
finishReason: mapFinishReason(result.finishReason),
|
|
105
|
+
usage: mapUsage(result.usage),
|
|
106
|
+
providerMetadata: providerMetadata(result),
|
|
107
|
+
response: {
|
|
108
|
+
id: result.id || undefined,
|
|
109
|
+
modelId: result.model || undefined,
|
|
110
|
+
},
|
|
111
|
+
warnings,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type ProviderV4 } from "@ai-sdk/provider";
|
|
2
|
+
import type { Idemcore } from "@idemcore/sdk";
|
|
3
|
+
import type { ClientOptions } from "@idemcore/sdk";
|
|
4
|
+
import { IdemcoreLanguageModel } from "./language-model.js";
|
|
5
|
+
export type IdemcoreProviderSettings = ClientOptions & {
|
|
6
|
+
/** Default receipt privacy (`hash_only` or `full`). */
|
|
7
|
+
privacy?: "hash_only" | "full";
|
|
8
|
+
/** Injected client (tests). */
|
|
9
|
+
client?: Idemcore;
|
|
10
|
+
};
|
|
11
|
+
export type IdemcoreProvider = ProviderV4 & {
|
|
12
|
+
(modelId: string): IdemcoreLanguageModel;
|
|
13
|
+
chat(modelId: string): IdemcoreLanguageModel;
|
|
14
|
+
};
|
|
15
|
+
export declare function createIdemcore(options?: IdemcoreProviderSettings): IdemcoreProvider;
|
|
16
|
+
/** Default provider that reads IDEMCORE_API_KEY from the environment. */
|
|
17
|
+
export declare const idemcore: IdemcoreProvider;
|
package/dist/provider.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { NoSuchModelError } from "@ai-sdk/provider";
|
|
2
|
+
import { IdemcoreLanguageModel } from "./language-model.js";
|
|
3
|
+
function unsupported(modelType, modelId) {
|
|
4
|
+
throw new NoSuchModelError({
|
|
5
|
+
modelId,
|
|
6
|
+
modelType,
|
|
7
|
+
message: `Idemcore does not offer ${modelType} '${modelId}'.`,
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
export function createIdemcore(options = {}) {
|
|
11
|
+
const languageModel = (modelId) => new IdemcoreLanguageModel(modelId, options);
|
|
12
|
+
const provider = Object.assign(function idemcore(modelId) {
|
|
13
|
+
return languageModel(modelId);
|
|
14
|
+
}, {
|
|
15
|
+
specificationVersion: "v4",
|
|
16
|
+
languageModel,
|
|
17
|
+
chat: languageModel,
|
|
18
|
+
embeddingModel: (modelId) => unsupported("embeddingModel", modelId),
|
|
19
|
+
imageModel: (modelId) => unsupported("imageModel", modelId),
|
|
20
|
+
});
|
|
21
|
+
return provider;
|
|
22
|
+
}
|
|
23
|
+
/** Default provider that reads IDEMCORE_API_KEY from the environment. */
|
|
24
|
+
export const idemcore = createIdemcore();
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ChatResult } from "@idemcore/sdk";
|
|
2
|
+
import type { JSONObject, SharedV4ProviderMetadata } from "@ai-sdk/provider";
|
|
3
|
+
export type IdemcoreReceiptMetadata = {
|
|
4
|
+
receiptId: string;
|
|
5
|
+
receipt: JSONObject;
|
|
6
|
+
salt: string | null;
|
|
7
|
+
privacy: string;
|
|
8
|
+
model: string;
|
|
9
|
+
finishReason: string | null;
|
|
10
|
+
id: string;
|
|
11
|
+
};
|
|
12
|
+
export declare function receiptMetadata(result: ChatResult): IdemcoreReceiptMetadata;
|
|
13
|
+
export declare function providerMetadata(result: ChatResult): SharedV4ProviderMetadata;
|
package/dist/receipt.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export function receiptMetadata(result) {
|
|
2
|
+
return {
|
|
3
|
+
receiptId: result.receiptId,
|
|
4
|
+
receipt: (result.receipt ?? {}),
|
|
5
|
+
salt: result.salt,
|
|
6
|
+
privacy: result.privacy,
|
|
7
|
+
model: result.model,
|
|
8
|
+
finishReason: result.finishReason,
|
|
9
|
+
id: result.id,
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export function providerMetadata(result) {
|
|
13
|
+
return {
|
|
14
|
+
idemcore: receiptMetadata(result),
|
|
15
|
+
};
|
|
16
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,48 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@idemcore/ai-sdk-provider",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Vercel AI SDK provider for Idemcore, exposing signed verifiable receipts via providerMetadata.",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"README.md"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsc -p tsconfig.build.json",
|
|
21
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
22
|
+
"test": "tsx --test tests/*.test.ts"
|
|
23
|
+
},
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=18"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@ai-sdk/provider": "^4.0.0",
|
|
29
|
+
"@idemcore/sdk": "0.3.0"
|
|
30
|
+
},
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"ai": ">=7.0.0"
|
|
33
|
+
},
|
|
34
|
+
"peerDependenciesMeta": {
|
|
35
|
+
"ai": {
|
|
36
|
+
"optional": true
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@types/node": "^24.0.0",
|
|
41
|
+
"ai": "^7.0.0",
|
|
42
|
+
"tsx": "^4.19.0",
|
|
43
|
+
"typescript": "^5.6.0"
|
|
44
|
+
},
|
|
45
|
+
"publishConfig": {
|
|
46
|
+
"access": "public"
|
|
47
|
+
}
|
|
48
|
+
}
|