@arcote.tech/arc-ai 0.5.1 → 0.5.5
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/package.json +3 -3
- package/src/aggregates/credit-ledger.ts +178 -0
- package/src/ai-builder.ts +19 -36
- package/src/billing/index.ts +5 -0
- package/src/billing/strategies/fixed-markup.ts +32 -0
- package/src/billing/strategies/flat-rate.ts +19 -0
- package/src/billing/strategies/index.ts +3 -0
- package/src/billing/strategies/token-pass.ts +15 -0
- package/src/billing/types.ts +24 -0
- package/src/billing/wrap-provider.ts +52 -0
- package/src/index.ts +21 -9
- package/src/tool/tool.ts +139 -53
- package/src/types.ts +100 -23
- package/src/aggregates/budget.ts +0 -137
- package/src/aggregates/completion.ts +0 -235
- package/src/routes/stream-completion.ts +0 -94
|
@@ -1,235 +0,0 @@
|
|
|
1
|
-
/// <reference path="../arc.d.ts" />
|
|
2
|
-
import {
|
|
3
|
-
aggregate,
|
|
4
|
-
date,
|
|
5
|
-
id,
|
|
6
|
-
number,
|
|
7
|
-
string,
|
|
8
|
-
type ArcId,
|
|
9
|
-
} from "@arcote.tech/arc";
|
|
10
|
-
import type { Token } from "@arcote.tech/arc-auth";
|
|
11
|
-
import type { LLMProvider, PricingConfig, TokenUsage } from "../types";
|
|
12
|
-
|
|
13
|
-
// ─── ID ──────────────────────────────────────────────────────────
|
|
14
|
-
|
|
15
|
-
export const createCompletionId = <const Name extends string>(data: {
|
|
16
|
-
name: Name;
|
|
17
|
-
}) => id(`${data.name}Completion`);
|
|
18
|
-
|
|
19
|
-
export type CompletionId<Name extends string = string> = ReturnType<
|
|
20
|
-
typeof createCompletionId<Name>
|
|
21
|
-
>;
|
|
22
|
-
|
|
23
|
-
// ─── Aggregate ───────────────────────────────────────────────────
|
|
24
|
-
|
|
25
|
-
export type CompletionAggregateData = {
|
|
26
|
-
name: string;
|
|
27
|
-
completionId: ArcId<any>;
|
|
28
|
-
accountId: ArcId<any>;
|
|
29
|
-
userToken: Token;
|
|
30
|
-
providers: LLMProvider[];
|
|
31
|
-
pricing?: Record<string, PricingConfig>;
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
export const createCompletionAggregate = <
|
|
35
|
-
const Data extends CompletionAggregateData,
|
|
36
|
-
>(
|
|
37
|
-
data: Data,
|
|
38
|
-
) => {
|
|
39
|
-
const { completionId, accountId, userToken, providers, pricing } = data;
|
|
40
|
-
|
|
41
|
-
function resolveProvider(model: string): LLMProvider | undefined {
|
|
42
|
-
return providers.find((p) => p.models.includes(model));
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function calculateCostCents(
|
|
46
|
-
model: string,
|
|
47
|
-
usage: TokenUsage,
|
|
48
|
-
): number {
|
|
49
|
-
const config = pricing?.[model];
|
|
50
|
-
if (!config) return 0;
|
|
51
|
-
|
|
52
|
-
const inputCost =
|
|
53
|
-
(usage.inputTokens / 1_000_000) * config.inputPer1M;
|
|
54
|
-
const outputCost =
|
|
55
|
-
(usage.outputTokens / 1_000_000) * config.outputPer1M;
|
|
56
|
-
const cachedCost = config.cachedInputPer1M
|
|
57
|
-
? (usage.cachedTokens / 1_000_000) * config.cachedInputPer1M
|
|
58
|
-
: 0;
|
|
59
|
-
const reasoningCost = config.reasoningPer1M
|
|
60
|
-
? (usage.reasoningTokens / 1_000_000) * config.reasoningPer1M
|
|
61
|
-
: 0;
|
|
62
|
-
|
|
63
|
-
return Math.round((inputCost + outputCost + cachedCost + reasoningCost) * 100);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
return aggregate(
|
|
67
|
-
`${data.name}Completions`,
|
|
68
|
-
completionId,
|
|
69
|
-
{
|
|
70
|
-
accountId,
|
|
71
|
-
model: string(),
|
|
72
|
-
provider: string(),
|
|
73
|
-
status: string(),
|
|
74
|
-
promptTokens: number(),
|
|
75
|
-
completionTokens: number(),
|
|
76
|
-
totalTokens: number(),
|
|
77
|
-
costCents: number(),
|
|
78
|
-
finishReason: string().optional(),
|
|
79
|
-
errorMessage: string().optional(),
|
|
80
|
-
requestedAt: date(),
|
|
81
|
-
completedAt: date().optional(),
|
|
82
|
-
metadata: string().optional(),
|
|
83
|
-
},
|
|
84
|
-
)
|
|
85
|
-
.publicEvent(
|
|
86
|
-
"completionRequested",
|
|
87
|
-
{
|
|
88
|
-
completionId,
|
|
89
|
-
accountId,
|
|
90
|
-
model: string(),
|
|
91
|
-
provider: string(),
|
|
92
|
-
metadata: string().optional(),
|
|
93
|
-
},
|
|
94
|
-
async (ctx, event) => {
|
|
95
|
-
const { completionId: cId, accountId: aId, model, provider, metadata } =
|
|
96
|
-
event.payload;
|
|
97
|
-
await ctx.set(cId, {
|
|
98
|
-
accountId: aId,
|
|
99
|
-
model,
|
|
100
|
-
provider,
|
|
101
|
-
status: "pending",
|
|
102
|
-
promptTokens: 0,
|
|
103
|
-
completionTokens: 0,
|
|
104
|
-
totalTokens: 0,
|
|
105
|
-
costCents: 0,
|
|
106
|
-
requestedAt: event.createdAt,
|
|
107
|
-
metadata,
|
|
108
|
-
});
|
|
109
|
-
},
|
|
110
|
-
)
|
|
111
|
-
|
|
112
|
-
.publicEvent(
|
|
113
|
-
"completionFinished",
|
|
114
|
-
{
|
|
115
|
-
completionId,
|
|
116
|
-
promptTokens: number(),
|
|
117
|
-
completionTokens: number(),
|
|
118
|
-
totalTokens: number(),
|
|
119
|
-
costCents: number(),
|
|
120
|
-
finishReason: string(),
|
|
121
|
-
},
|
|
122
|
-
async (ctx, event) => {
|
|
123
|
-
const p = event.payload;
|
|
124
|
-
await ctx.modify(p.completionId, {
|
|
125
|
-
status: "completed",
|
|
126
|
-
promptTokens: p.promptTokens,
|
|
127
|
-
completionTokens: p.completionTokens,
|
|
128
|
-
totalTokens: p.totalTokens,
|
|
129
|
-
costCents: p.costCents,
|
|
130
|
-
finishReason: p.finishReason,
|
|
131
|
-
completedAt: event.createdAt,
|
|
132
|
-
});
|
|
133
|
-
},
|
|
134
|
-
)
|
|
135
|
-
|
|
136
|
-
.publicEvent(
|
|
137
|
-
"completionFailed",
|
|
138
|
-
{
|
|
139
|
-
completionId,
|
|
140
|
-
errorMessage: string(),
|
|
141
|
-
},
|
|
142
|
-
async (ctx, event) => {
|
|
143
|
-
await ctx.modify(event.payload.completionId, {
|
|
144
|
-
status: "error",
|
|
145
|
-
errorMessage: event.payload.errorMessage,
|
|
146
|
-
completedAt: event.createdAt,
|
|
147
|
-
});
|
|
148
|
-
},
|
|
149
|
-
)
|
|
150
|
-
|
|
151
|
-
.mutateMethod(
|
|
152
|
-
"requestCompletion",
|
|
153
|
-
(fn) => fn.withParams({
|
|
154
|
-
model: string(),
|
|
155
|
-
messages: string(),
|
|
156
|
-
tools: string().optional(),
|
|
157
|
-
webSearch: string().optional(),
|
|
158
|
-
metadata: string().optional(),
|
|
159
|
-
}).handle(
|
|
160
|
-
ONLY_SERVER &&
|
|
161
|
-
(async (ctx, params) => {
|
|
162
|
-
const provider = resolveProvider(params.model);
|
|
163
|
-
if (!provider) {
|
|
164
|
-
return { error: "PROVIDER_NOT_FOUND" as const };
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
const cId = completionId.generate();
|
|
168
|
-
const aId = ctx.$auth.params.accountId;
|
|
169
|
-
|
|
170
|
-
await ctx.completionRequested.emit({
|
|
171
|
-
completionId: cId,
|
|
172
|
-
accountId: accountId.parse(aId),
|
|
173
|
-
model: params.model,
|
|
174
|
-
provider: provider.name,
|
|
175
|
-
metadata: params.metadata,
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
try {
|
|
179
|
-
const messages = JSON.parse(params.messages);
|
|
180
|
-
const webSearch = params.webSearch === "true";
|
|
181
|
-
|
|
182
|
-
const toolDefs = params.tools
|
|
183
|
-
? JSON.parse(params.tools) as { name: string; description: string; parameters: Record<string, unknown> }[]
|
|
184
|
-
: undefined;
|
|
185
|
-
|
|
186
|
-
const result = await provider.complete({
|
|
187
|
-
model: params.model,
|
|
188
|
-
messages,
|
|
189
|
-
tools: toolDefs,
|
|
190
|
-
webSearch,
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
const costCents = calculateCostCents(params.model, result.usage);
|
|
194
|
-
|
|
195
|
-
await ctx.completionFinished.emit({
|
|
196
|
-
completionId: cId,
|
|
197
|
-
promptTokens: result.usage.inputTokens,
|
|
198
|
-
completionTokens: result.usage.outputTokens,
|
|
199
|
-
totalTokens: result.usage.totalTokens,
|
|
200
|
-
costCents,
|
|
201
|
-
finishReason: result.finishReason,
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
return {
|
|
205
|
-
completionId: cId,
|
|
206
|
-
content: result.content,
|
|
207
|
-
toolCalls: result.toolCalls,
|
|
208
|
-
finishReason: result.finishReason,
|
|
209
|
-
};
|
|
210
|
-
} catch (error) {
|
|
211
|
-
const errorMessage =
|
|
212
|
-
error instanceof Error ? error.message : "Unknown error";
|
|
213
|
-
|
|
214
|
-
await ctx.completionFailed.emit({
|
|
215
|
-
completionId: cId,
|
|
216
|
-
errorMessage,
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
return { error: errorMessage };
|
|
220
|
-
}
|
|
221
|
-
}),
|
|
222
|
-
))
|
|
223
|
-
|
|
224
|
-
.clientQuery("getAll", (fn) => fn.handle(async (ctx) => ctx.$query.find({})))
|
|
225
|
-
.clientQuery(
|
|
226
|
-
"getByAccount",
|
|
227
|
-
(fn) => fn.handle(async (ctx) =>
|
|
228
|
-
ctx.$query.find({ where: { accountId: ctx.$auth.params.accountId } }),
|
|
229
|
-
))
|
|
230
|
-
|
|
231
|
-
.protectBy(userToken, (p) => ({ accountId: p.accountId }))
|
|
232
|
-
;
|
|
233
|
-
};
|
|
234
|
-
|
|
235
|
-
export type CompletionAggregate = ReturnType<typeof createCompletionAggregate>;
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
import { route, type ArcAggregateElement } from "@arcote.tech/arc";
|
|
2
|
-
import type { Token } from "@arcote.tech/arc-auth";
|
|
3
|
-
import type { LLMProvider, Message, PricingConfig, StreamChunk } from "../types";
|
|
4
|
-
import type { ArcToolAny } from "../tool/tool";
|
|
5
|
-
|
|
6
|
-
export type StreamCompletionRouteData = {
|
|
7
|
-
name: string;
|
|
8
|
-
completionElement: ArcAggregateElement;
|
|
9
|
-
userToken: Token;
|
|
10
|
-
providers: LLMProvider[];
|
|
11
|
-
pricing?: Record<string, PricingConfig>;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
export const createStreamCompletionRoute = <
|
|
15
|
-
const Data extends StreamCompletionRouteData,
|
|
16
|
-
>(
|
|
17
|
-
data: Data,
|
|
18
|
-
) => {
|
|
19
|
-
const { completionElement, userToken, providers, pricing } = data;
|
|
20
|
-
|
|
21
|
-
function resolveProvider(model: string): LLMProvider | undefined {
|
|
22
|
-
return providers.find((p) => p.models.includes(model));
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
return route(`${data.name}AiStream`)
|
|
26
|
-
.path(`/ai/${data.name}/stream/:completionId`)
|
|
27
|
-
.protectBy(userToken, () => true)
|
|
28
|
-
.mutate([completionElement])
|
|
29
|
-
.handle({
|
|
30
|
-
POST: async (ctx, req) => {
|
|
31
|
-
const body = await req.json() as {
|
|
32
|
-
model: string;
|
|
33
|
-
messages: Message[];
|
|
34
|
-
tools?: { name: string; description: string; parameters: Record<string, unknown> }[];
|
|
35
|
-
webSearch?: boolean;
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
const provider = resolveProvider(body.model);
|
|
39
|
-
if (!provider) {
|
|
40
|
-
return new Response(
|
|
41
|
-
JSON.stringify({ error: "PROVIDER_NOT_FOUND" }),
|
|
42
|
-
{ status: 400, headers: { "Content-Type": "application/json" } },
|
|
43
|
-
);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const encoder = new TextEncoder();
|
|
47
|
-
|
|
48
|
-
const stream = new ReadableStream({
|
|
49
|
-
async start(controller) {
|
|
50
|
-
const sendEvent = (chunk: StreamChunk) => {
|
|
51
|
-
controller.enqueue(
|
|
52
|
-
encoder.encode(`data: ${JSON.stringify(chunk)}\n\n`),
|
|
53
|
-
);
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
try {
|
|
57
|
-
const result = await provider.streamComplete(
|
|
58
|
-
{
|
|
59
|
-
model: body.model,
|
|
60
|
-
messages: body.messages,
|
|
61
|
-
tools: body.tools,
|
|
62
|
-
webSearch: body.webSearch,
|
|
63
|
-
},
|
|
64
|
-
sendEvent,
|
|
65
|
-
);
|
|
66
|
-
|
|
67
|
-
// Send final done event with usage
|
|
68
|
-
sendEvent({
|
|
69
|
-
type: "done",
|
|
70
|
-
usage: result.usage,
|
|
71
|
-
finishReason: result.finishReason,
|
|
72
|
-
});
|
|
73
|
-
} catch (error) {
|
|
74
|
-
sendEvent({
|
|
75
|
-
type: "error",
|
|
76
|
-
content:
|
|
77
|
-
error instanceof Error ? error.message : "Unknown error",
|
|
78
|
-
});
|
|
79
|
-
} finally {
|
|
80
|
-
controller.close();
|
|
81
|
-
}
|
|
82
|
-
},
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
return new Response(stream, {
|
|
86
|
-
headers: {
|
|
87
|
-
"Content-Type": "text/event-stream",
|
|
88
|
-
"Cache-Control": "no-cache",
|
|
89
|
-
Connection: "keep-alive",
|
|
90
|
-
},
|
|
91
|
-
});
|
|
92
|
-
},
|
|
93
|
-
});
|
|
94
|
-
};
|