@arcote.tech/arc-ai 0.4.6

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 ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "@arcote.tech/arc-ai",
3
+ "type": "module",
4
+ "version": "0.4.6",
5
+ "private": false,
6
+ "description": "AI provider abstraction, completion tracking, and budget management for Arc framework",
7
+ "main": "./src/index.ts",
8
+ "types": "./src/index.ts",
9
+ "scripts": {
10
+ "type-check": "tsc --noEmit"
11
+ },
12
+ "peerDependencies": {
13
+ "@arcote.tech/arc": "^0.4.6",
14
+ "@arcote.tech/arc-auth": "^0.4.6",
15
+ "typescript": "^5.0.0"
16
+ },
17
+ "devDependencies": {
18
+ "@types/bun": "latest"
19
+ }
20
+ }
@@ -0,0 +1,139 @@
1
+ /// <reference path="../arc.d.ts" />
2
+ import {
3
+ aggregate,
4
+ boolean,
5
+ id,
6
+ number,
7
+ string,
8
+ type AggregateConstructorAny,
9
+ type ArcId,
10
+ } from "@arcote.tech/arc";
11
+ import type { Token } from "@arcote.tech/arc-auth";
12
+
13
+ // ─── ID ──────────────────────────────────────────────────────────
14
+
15
+ export const createBudgetId = <const Name extends string>(data: {
16
+ name: Name;
17
+ }) => id(`${data.name}Budget`);
18
+
19
+ export type BudgetId<Name extends string = string> = ReturnType<
20
+ typeof createBudgetId<Name>
21
+ >;
22
+
23
+ // ─── Aggregate ───────────────────────────────────────────────────
24
+
25
+ export type BudgetAggregateData = {
26
+ name: string;
27
+ budgetId: ArcId<any>;
28
+ accountId: ArcId<any>;
29
+ userToken: Token;
30
+ CompletionAggregate: AggregateConstructorAny;
31
+ };
32
+
33
+ export const createBudgetAggregate = <
34
+ const Data extends BudgetAggregateData,
35
+ >(
36
+ data: Data,
37
+ ) => {
38
+ const { budgetId, accountId, userToken, CompletionAggregate } = data;
39
+
40
+ const completionFinishedEvent =
41
+ CompletionAggregate.getEvent("completionFinished");
42
+
43
+ return aggregate(`${data.name}Budgets`, budgetId, {
44
+ accountId,
45
+ budgetType: string(),
46
+ limitCents: number(),
47
+ usedCents: number(),
48
+ period: string().optional(),
49
+ isExceeded: boolean(),
50
+ })
51
+ .publicEvent(
52
+ "budgetSet",
53
+ {
54
+ budgetId,
55
+ accountId,
56
+ budgetType: string(),
57
+ limitCents: number(),
58
+ period: string().optional(),
59
+ },
60
+ async (ctx, event) => {
61
+ const p = event.payload;
62
+ await ctx.set(p.budgetId, {
63
+ accountId: p.accountId,
64
+ budgetType: p.budgetType,
65
+ limitCents: p.limitCents,
66
+ usedCents: 0,
67
+ period: p.period,
68
+ isExceeded: false,
69
+ });
70
+ },
71
+ )
72
+
73
+ .publicEvent(
74
+ "budgetUsageRecorded",
75
+ {
76
+ budgetId,
77
+ amountCents: number(),
78
+ },
79
+ async (ctx, event) => {
80
+ const existing = await ctx.findOne({ _id: event.payload.budgetId });
81
+ if (!existing) return;
82
+
83
+ const newUsed = existing.usedCents + event.payload.amountCents;
84
+ await ctx.modify(event.payload.budgetId, {
85
+ usedCents: newUsed,
86
+ isExceeded: newUsed >= existing.limitCents,
87
+ });
88
+ },
89
+ )
90
+
91
+ .handleEvent(completionFinishedEvent, async (ctx, event) => {
92
+ // Record usage from completion cost on all matching budgets
93
+ const budgets = await ctx.find({});
94
+ for (const budget of budgets) {
95
+ const newUsed = budget.usedCents + event.payload.costCents;
96
+ await ctx.modify(budget._id, {
97
+ usedCents: newUsed,
98
+ isExceeded: newUsed >= budget.limitCents,
99
+ });
100
+ }
101
+ })
102
+
103
+ .mutateMethod(
104
+ "setBudget",
105
+ {
106
+ params: {
107
+ budgetType: string(),
108
+ limitCents: number(),
109
+ period: string().optional(),
110
+ },
111
+ },
112
+ ONLY_SERVER &&
113
+ (async (ctx, params) => {
114
+ const bId = budgetId.generate();
115
+ const aId = ctx.$auth.params.accountId;
116
+
117
+ await ctx.budgetSet.emit({
118
+ budgetId: bId,
119
+ accountId: accountId.parse(aId),
120
+ budgetType: params.budgetType,
121
+ limitCents: params.limitCents,
122
+ period: params.period,
123
+ });
124
+
125
+ return { budgetId: bId };
126
+ }),
127
+ )
128
+
129
+ .clientQuery(
130
+ "getByAccount",
131
+ async (ctx) =>
132
+ ctx.$query.find({ where: { accountId: ctx.$auth.params.accountId } }),
133
+ )
134
+
135
+ .protectBy(userToken, (p) => ({ accountId: p.accountId }))
136
+ .build();
137
+ };
138
+
139
+ export type BudgetAggregate = ReturnType<typeof createBudgetAggregate>;
@@ -0,0 +1,237 @@
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
+ {
154
+ params: {
155
+ model: string(),
156
+ messages: string(),
157
+ tools: string().optional(),
158
+ webSearch: string().optional(),
159
+ metadata: string().optional(),
160
+ },
161
+ },
162
+ ONLY_SERVER &&
163
+ (async (ctx, params) => {
164
+ const provider = resolveProvider(params.model);
165
+ if (!provider) {
166
+ return { error: "PROVIDER_NOT_FOUND" as const };
167
+ }
168
+
169
+ const cId = completionId.generate();
170
+ const aId = ctx.$auth.params.accountId;
171
+
172
+ await ctx.completionRequested.emit({
173
+ completionId: cId,
174
+ accountId: accountId.parse(aId),
175
+ model: params.model,
176
+ provider: provider.name,
177
+ metadata: params.metadata,
178
+ });
179
+
180
+ try {
181
+ const messages = JSON.parse(params.messages);
182
+ const webSearch = params.webSearch === "true";
183
+
184
+ const toolDefs = params.tools
185
+ ? JSON.parse(params.tools) as { name: string; description: string; parameters: Record<string, unknown> }[]
186
+ : undefined;
187
+
188
+ const result = await provider.complete({
189
+ model: params.model,
190
+ messages,
191
+ tools: toolDefs,
192
+ webSearch,
193
+ });
194
+
195
+ const costCents = calculateCostCents(params.model, result.usage);
196
+
197
+ await ctx.completionFinished.emit({
198
+ completionId: cId,
199
+ promptTokens: result.usage.inputTokens,
200
+ completionTokens: result.usage.outputTokens,
201
+ totalTokens: result.usage.totalTokens,
202
+ costCents,
203
+ finishReason: result.finishReason,
204
+ });
205
+
206
+ return {
207
+ completionId: cId,
208
+ content: result.content,
209
+ toolCalls: result.toolCalls,
210
+ finishReason: result.finishReason,
211
+ };
212
+ } catch (error) {
213
+ const errorMessage =
214
+ error instanceof Error ? error.message : "Unknown error";
215
+
216
+ await ctx.completionFailed.emit({
217
+ completionId: cId,
218
+ errorMessage,
219
+ });
220
+
221
+ return { error: errorMessage };
222
+ }
223
+ }),
224
+ )
225
+
226
+ .clientQuery("getAll", async (ctx) => ctx.$query.find({}))
227
+ .clientQuery(
228
+ "getByAccount",
229
+ async (ctx) =>
230
+ ctx.$query.find({ where: { accountId: ctx.$auth.params.accountId } }),
231
+ )
232
+
233
+ .protectBy(userToken, (p) => ({ accountId: p.accountId }))
234
+ .build();
235
+ };
236
+
237
+ export type CompletionAggregate = ReturnType<typeof createCompletionAggregate>;
@@ -0,0 +1,97 @@
1
+ import {
2
+ aggregateContextElement,
3
+ context,
4
+ type AggregateConstructorAny,
5
+ type ArcContextElement,
6
+ } from "@arcote.tech/arc";
7
+ import type { AccountId, Token } from "@arcote.tech/arc-auth";
8
+ import { createCompletionId, createCompletionAggregate } from "./aggregates/completion";
9
+ import { createBudgetId, createBudgetAggregate } from "./aggregates/budget";
10
+ import { createStreamCompletionRoute } from "./routes/stream-completion";
11
+ import type { LLMProvider, PricingConfig } from "./types";
12
+
13
+ export class AIBuilder<
14
+ CompId,
15
+ BudId,
16
+ Completion extends AggregateConstructorAny,
17
+ Budget extends AggregateConstructorAny,
18
+ Providers extends LLMProvider[],
19
+ Elements extends ArcContextElement<any>[],
20
+ > {
21
+ constructor(
22
+ private readonly _name: string,
23
+ readonly completionId: CompId,
24
+ readonly budgetId: BudId,
25
+ readonly Completion: Completion,
26
+ readonly Budget: Budget,
27
+ readonly providers: Providers,
28
+ readonly elements: Elements,
29
+ ) {}
30
+
31
+ build() {
32
+ return {
33
+ context: context(this.elements),
34
+ completionId: this.completionId,
35
+ budgetId: this.budgetId,
36
+ Completion: this.Completion,
37
+ Budget: this.Budget,
38
+ providers: this.providers,
39
+ resolveProvider: (model: string) =>
40
+ this.providers.find((p) => p.models.includes(model)),
41
+ };
42
+ }
43
+ }
44
+
45
+ export function ai<
46
+ const Name extends string,
47
+ const Secret extends string | undefined,
48
+ >(config: {
49
+ name: Name;
50
+ secret: Secret;
51
+ accountId: AccountId;
52
+ userToken: Token;
53
+ providers: LLMProvider[];
54
+ pricing?: Record<string, PricingConfig>;
55
+ }) {
56
+ const completionId = createCompletionId({ name: config.name });
57
+ const budgetId = createBudgetId({ name: config.name });
58
+
59
+ const Completion = createCompletionAggregate({
60
+ name: config.name,
61
+ completionId,
62
+ accountId: config.accountId,
63
+ userToken: config.userToken,
64
+ providers: config.providers,
65
+ pricing: config.pricing,
66
+ });
67
+ const completionElement = aggregateContextElement(Completion);
68
+
69
+ const Budget = createBudgetAggregate({
70
+ name: config.name,
71
+ budgetId,
72
+ accountId: config.accountId,
73
+ userToken: config.userToken,
74
+ CompletionAggregate: Completion,
75
+ });
76
+ const budgetElement = aggregateContextElement(Budget);
77
+
78
+ const streamRoute = createStreamCompletionRoute({
79
+ name: config.name,
80
+ completionElement,
81
+ userToken: config.userToken,
82
+ providers: config.providers,
83
+ pricing: config.pricing,
84
+ });
85
+
86
+ const elements = [completionElement, budgetElement, streamRoute];
87
+
88
+ return new AIBuilder(
89
+ config.name,
90
+ completionId,
91
+ budgetId,
92
+ Completion,
93
+ Budget,
94
+ config.providers,
95
+ elements,
96
+ );
97
+ }
package/src/arc.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ declare const BROWSER: boolean;
2
+ declare const NOT_ON_BROWSER: boolean;
3
+ declare const ONLY_BROWSER: boolean;
4
+ declare const SERVER: boolean;
5
+ declare const NOT_ON_SERVER: boolean;
6
+ declare const ONLY_SERVER: boolean;
package/src/index.ts ADDED
@@ -0,0 +1,32 @@
1
+ // --- Builder API ---
2
+ export { ai, AIBuilder } from "./ai-builder";
3
+
4
+ // --- Tool helper ---
5
+ export { tool, ArcTool } from "./tool/tool";
6
+ export type { ArcToolAny, JsonSchemaToolDef } from "./tool/tool";
7
+
8
+ // --- Aggregate factories & types ---
9
+ export { createCompletionAggregate, createCompletionId } from "./aggregates/completion";
10
+ export type { CompletionAggregate, CompletionId } from "./aggregates/completion";
11
+ export { createBudgetAggregate, createBudgetId } from "./aggregates/budget";
12
+ export type { BudgetAggregate, BudgetId } from "./aggregates/budget";
13
+
14
+ // --- Route ---
15
+ export { createStreamCompletionRoute } from "./routes/stream-completion";
16
+
17
+ // --- Provider types ---
18
+ export type {
19
+ LLMProvider,
20
+ ProviderName,
21
+ CompletionRequest,
22
+ CompletionResult,
23
+ StreamChunk,
24
+ StreamEventType,
25
+ Message,
26
+ MessageRole,
27
+ ToolCall,
28
+ ToolResult,
29
+ TokenUsage,
30
+ FinishReason,
31
+ PricingConfig,
32
+ } from "./types";
@@ -0,0 +1,94 @@
1
+ import { route, type ArcAggregateElement, type AggregateConstructorAny } from "@arcote.tech/arc";
2
+ import type { Token } from "@arcote.tech/arc-auth";
3
+ import type { LLMProvider, PricingConfig, StreamChunk } from "../types";
4
+ import type { ArcToolAny } from "../tool/tool";
5
+
6
+ export type StreamCompletionRouteData = {
7
+ name: string;
8
+ completionElement: ArcAggregateElement<AggregateConstructorAny>;
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: { role: string; content: string }[];
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 as any,
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
+ };
@@ -0,0 +1,76 @@
1
+ import { object, type ArcRawShape, type $type } from "@arcote.tech/arc";
2
+
3
+ // ─── JSON Schema Tool Definition (sent to LLM) ──────────────────
4
+
5
+ export interface JsonSchemaToolDef {
6
+ name: string;
7
+ description: string;
8
+ parameters: Record<string, unknown>;
9
+ }
10
+
11
+ // ─── ArcTool ─────────────────────────────────────────────────────
12
+
13
+ export class ArcTool<
14
+ const Name extends string = string,
15
+ const S extends ArcRawShape = ArcRawShape,
16
+ > {
17
+ readonly name: Name;
18
+ #description: string = "";
19
+ #schema?: S;
20
+ #handler?: (params: $type<ReturnType<typeof object<S>>>) => Promise<string>;
21
+
22
+ constructor(name: Name) {
23
+ this.name = name;
24
+ }
25
+
26
+ description<const D extends string>(desc: D) {
27
+ this.#description = desc;
28
+ return this;
29
+ }
30
+
31
+ schema<const NewS extends ArcRawShape>(shape: NewS) {
32
+ const next = new ArcTool<Name, NewS>(this.name);
33
+ next.#description = this.#description;
34
+ next.#schema = shape;
35
+ return next;
36
+ }
37
+
38
+ handle(handler: (params: $type<ReturnType<typeof object<S>>>) => Promise<string>) {
39
+ this.#handler = handler;
40
+ return this;
41
+ }
42
+
43
+ /**
44
+ * Convert Arc schema to JSON Schema for LLM tool definition
45
+ */
46
+ toJsonSchema(): JsonSchemaToolDef {
47
+ const parametersSchema = this.#schema
48
+ ? object(this.#schema).toJsonSchema()
49
+ : { type: "object", properties: {} };
50
+
51
+ return {
52
+ name: this.name,
53
+ description: this.#description,
54
+ parameters: parametersSchema,
55
+ };
56
+ }
57
+
58
+ /**
59
+ * Execute the tool handler
60
+ */
61
+ async execute(params: Record<string, unknown>): Promise<string> {
62
+ if (!this.#handler) {
63
+ throw new Error(`Tool "${this.name}" has no handler`);
64
+ }
65
+
66
+ return this.#handler(params as any);
67
+ }
68
+ }
69
+
70
+ // ─── Factory ─────────────────────────────────────────────────────
71
+
72
+ export function tool<const Name extends string>(name: Name) {
73
+ return new ArcTool(name);
74
+ }
75
+
76
+ export type ArcToolAny = ArcTool<string, ArcRawShape>;
package/src/types.ts ADDED
@@ -0,0 +1,102 @@
1
+ import type { JsonSchemaToolDef } from "./tool/tool";
2
+
3
+ // ─── Provider Names ──────────────────────────────────────────────
4
+
5
+ export type ProviderName = "openai" | "gemini" | "claude";
6
+
7
+ // ─── Messages ────────────────────────────────────────────────────
8
+
9
+ export type MessageRole = "system" | "user" | "assistant" | "tool";
10
+
11
+ export interface Message {
12
+ role: MessageRole;
13
+ content: string;
14
+ name?: string;
15
+ toolCallId?: string;
16
+ }
17
+
18
+ // ─── Tool Calls ──────────────────────────────────────────────────
19
+
20
+ export interface ToolCall {
21
+ id: string;
22
+ name: string;
23
+ arguments: Record<string, unknown>;
24
+ }
25
+
26
+ export interface ToolResult {
27
+ toolCallId: string;
28
+ name: string;
29
+ content: string;
30
+ isError: boolean;
31
+ }
32
+
33
+ // ─── Token Usage ─────────────────────────────────────────────────
34
+
35
+ export interface TokenUsage {
36
+ inputTokens: number;
37
+ outputTokens: number;
38
+ totalTokens: number;
39
+ cachedTokens: number;
40
+ reasoningTokens: number;
41
+ }
42
+
43
+ // ─── Completion ──────────────────────────────────────────────────
44
+
45
+ export type FinishReason = "stop" | "tool_call" | "max_tokens" | "error";
46
+
47
+ export interface CompletionRequest {
48
+ model: string;
49
+ messages: Message[];
50
+ tools?: JsonSchemaToolDef[];
51
+ webSearch?: boolean;
52
+ temperature?: number;
53
+ maxTokens?: number;
54
+ }
55
+
56
+ export interface CompletionResult {
57
+ content: string;
58
+ toolCalls: ToolCall[];
59
+ usage: TokenUsage;
60
+ finishReason: FinishReason;
61
+ }
62
+
63
+ // ─── Streaming ───────────────────────────────────────────────────
64
+
65
+ export type StreamEventType =
66
+ | "content_delta"
67
+ | "tool_call_start"
68
+ | "tool_call_delta"
69
+ | "tool_result"
70
+ | "usage_update"
71
+ | "done"
72
+ | "error";
73
+
74
+ export interface StreamChunk {
75
+ type: StreamEventType;
76
+ content?: string;
77
+ toolCall?: ToolCall;
78
+ toolResult?: ToolResult;
79
+ usage?: TokenUsage;
80
+ finishReason?: FinishReason;
81
+ }
82
+
83
+ // ─── LLM Provider ────────────────────────────────────────────────
84
+
85
+ export interface LLMProvider {
86
+ name: ProviderName;
87
+ models: string[];
88
+ complete(request: CompletionRequest): Promise<CompletionResult>;
89
+ streamComplete(
90
+ request: CompletionRequest,
91
+ onChunk: (chunk: StreamChunk) => void,
92
+ ): Promise<CompletionResult>;
93
+ }
94
+
95
+ // ─── Pricing ─────────────────────────────────────────────────────
96
+
97
+ export interface PricingConfig {
98
+ inputPer1M: number;
99
+ outputPer1M: number;
100
+ cachedInputPer1M?: number;
101
+ reasoningPer1M?: number;
102
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,4 @@
1
+ {
2
+ "extends": "../../../../tsconfig.json",
3
+ "include": ["src/**/*"]
4
+ }