@arcote.tech/arc-ai 0.4.9 → 0.5.1
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/budget.ts +7 -9
- package/src/aggregates/completion.ts +6 -8
- package/src/ai-builder.ts +21 -62
- package/src/index.ts +5 -5
- package/src/routes/stream-completion.ts +3 -3
- package/src/tool/tool.ts +72 -27
- package/src/types.ts +26 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arcote.tech/arc-ai",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.5.1",
|
|
5
5
|
"private": false,
|
|
6
6
|
"description": "AI provider abstraction, completion tracking, and budget management for Arc framework",
|
|
7
7
|
"main": "./src/index.ts",
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
"type-check": "tsc --noEmit"
|
|
11
11
|
},
|
|
12
12
|
"peerDependencies": {
|
|
13
|
-
"@arcote.tech/arc": "^0.
|
|
14
|
-
"@arcote.tech/arc-auth": "^0.
|
|
13
|
+
"@arcote.tech/arc": "^0.5.1",
|
|
14
|
+
"@arcote.tech/arc-auth": "^0.5.1",
|
|
15
15
|
"typescript": "^5.0.0"
|
|
16
16
|
},
|
|
17
17
|
"devDependencies": {
|
package/src/aggregates/budget.ts
CHANGED
|
@@ -5,10 +5,10 @@ import {
|
|
|
5
5
|
id,
|
|
6
6
|
number,
|
|
7
7
|
string,
|
|
8
|
-
type ArcAggregateElement,
|
|
9
8
|
type ArcId,
|
|
10
9
|
} from "@arcote.tech/arc";
|
|
11
10
|
import type { Token } from "@arcote.tech/arc-auth";
|
|
11
|
+
import type { createCompletionAggregate } from "./completion";
|
|
12
12
|
|
|
13
13
|
// ─── ID ──────────────────────────────────────────────────────────
|
|
14
14
|
|
|
@@ -27,7 +27,7 @@ export type BudgetAggregateData = {
|
|
|
27
27
|
budgetId: ArcId<any>;
|
|
28
28
|
accountId: ArcId<any>;
|
|
29
29
|
userToken: Token;
|
|
30
|
-
CompletionAggregate:
|
|
30
|
+
CompletionAggregate: ReturnType<typeof createCompletionAggregate>;
|
|
31
31
|
};
|
|
32
32
|
|
|
33
33
|
export const createBudgetAggregate = <
|
|
@@ -102,13 +102,11 @@ export const createBudgetAggregate = <
|
|
|
102
102
|
|
|
103
103
|
.mutateMethod(
|
|
104
104
|
"setBudget",
|
|
105
|
-
{
|
|
106
|
-
params: {
|
|
105
|
+
(fn) => fn.withParams({
|
|
107
106
|
budgetType: string(),
|
|
108
107
|
limitCents: number(),
|
|
109
108
|
period: string().optional(),
|
|
110
|
-
}
|
|
111
|
-
},
|
|
109
|
+
}).handle(
|
|
112
110
|
ONLY_SERVER &&
|
|
113
111
|
(async (ctx, params) => {
|
|
114
112
|
const bId = budgetId.generate();
|
|
@@ -124,13 +122,13 @@ export const createBudgetAggregate = <
|
|
|
124
122
|
|
|
125
123
|
return { budgetId: bId };
|
|
126
124
|
}),
|
|
127
|
-
)
|
|
125
|
+
))
|
|
128
126
|
|
|
129
127
|
.clientQuery(
|
|
130
128
|
"getByAccount",
|
|
131
|
-
async (ctx) =>
|
|
129
|
+
(fn) => fn.handle(async (ctx) =>
|
|
132
130
|
ctx.$query.find({ where: { accountId: ctx.$auth.params.accountId } }),
|
|
133
|
-
)
|
|
131
|
+
))
|
|
134
132
|
|
|
135
133
|
.protectBy(userToken, (p) => ({ accountId: p.accountId }))
|
|
136
134
|
;
|
|
@@ -150,15 +150,13 @@ export const createCompletionAggregate = <
|
|
|
150
150
|
|
|
151
151
|
.mutateMethod(
|
|
152
152
|
"requestCompletion",
|
|
153
|
-
{
|
|
154
|
-
params: {
|
|
153
|
+
(fn) => fn.withParams({
|
|
155
154
|
model: string(),
|
|
156
155
|
messages: string(),
|
|
157
156
|
tools: string().optional(),
|
|
158
157
|
webSearch: string().optional(),
|
|
159
158
|
metadata: string().optional(),
|
|
160
|
-
}
|
|
161
|
-
},
|
|
159
|
+
}).handle(
|
|
162
160
|
ONLY_SERVER &&
|
|
163
161
|
(async (ctx, params) => {
|
|
164
162
|
const provider = resolveProvider(params.model);
|
|
@@ -221,14 +219,14 @@ export const createCompletionAggregate = <
|
|
|
221
219
|
return { error: errorMessage };
|
|
222
220
|
}
|
|
223
221
|
}),
|
|
224
|
-
)
|
|
222
|
+
))
|
|
225
223
|
|
|
226
|
-
.clientQuery("getAll", async (ctx) => ctx.$query.find({}))
|
|
224
|
+
.clientQuery("getAll", (fn) => fn.handle(async (ctx) => ctx.$query.find({})))
|
|
227
225
|
.clientQuery(
|
|
228
226
|
"getByAccount",
|
|
229
|
-
async (ctx) =>
|
|
227
|
+
(fn) => fn.handle(async (ctx) =>
|
|
230
228
|
ctx.$query.find({ where: { accountId: ctx.$auth.params.accountId } }),
|
|
231
|
-
)
|
|
229
|
+
))
|
|
232
230
|
|
|
233
231
|
.protectBy(userToken, (p) => ({ accountId: p.accountId }))
|
|
234
232
|
;
|
package/src/ai-builder.ts
CHANGED
|
@@ -1,52 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
context,
|
|
3
|
-
type ArcAggregateElement,
|
|
4
|
-
type ArcContextElement,
|
|
5
|
-
} from "@arcote.tech/arc";
|
|
1
|
+
import { context, type ArcContextElement } from "@arcote.tech/arc";
|
|
6
2
|
import type { AccountId, Token } from "@arcote.tech/arc-auth";
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
3
|
+
import {
|
|
4
|
+
createCompletionId,
|
|
5
|
+
createCompletionAggregate,
|
|
6
|
+
} from "./aggregates/completion";
|
|
7
|
+
import {
|
|
8
|
+
createBudgetId,
|
|
9
|
+
createBudgetAggregate,
|
|
10
|
+
} from "./aggregates/budget";
|
|
10
11
|
import type { LLMProvider, PricingConfig } from "./types";
|
|
11
12
|
|
|
12
|
-
export
|
|
13
|
-
|
|
14
|
-
BudId,
|
|
15
|
-
Completion extends ArcAggregateElement,
|
|
16
|
-
Budget extends ArcAggregateElement,
|
|
17
|
-
Providers extends LLMProvider[],
|
|
18
|
-
Elements extends ArcContextElement<any>[],
|
|
19
|
-
> {
|
|
20
|
-
constructor(
|
|
21
|
-
private readonly _name: string,
|
|
22
|
-
readonly completionId: CompId,
|
|
23
|
-
readonly budgetId: BudId,
|
|
24
|
-
readonly Completion: Completion,
|
|
25
|
-
readonly Budget: Budget,
|
|
26
|
-
readonly providers: Providers,
|
|
27
|
-
readonly elements: Elements,
|
|
28
|
-
) {}
|
|
29
|
-
|
|
30
|
-
build() {
|
|
31
|
-
return {
|
|
32
|
-
context: context(this.elements),
|
|
33
|
-
completionId: this.completionId,
|
|
34
|
-
budgetId: this.budgetId,
|
|
35
|
-
Completion: this.Completion,
|
|
36
|
-
Budget: this.Budget,
|
|
37
|
-
providers: this.providers,
|
|
38
|
-
resolveProvider: (model: string) =>
|
|
39
|
-
this.providers.find((p) => p.models.includes(model)),
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export function ai<
|
|
45
|
-
const Name extends string,
|
|
46
|
-
const Secret extends string | undefined,
|
|
47
|
-
>(config: {
|
|
48
|
-
name: Name;
|
|
49
|
-
secret: Secret;
|
|
13
|
+
export function ai(config: {
|
|
14
|
+
name: string;
|
|
50
15
|
accountId: AccountId;
|
|
51
16
|
userToken: Token;
|
|
52
17
|
providers: LLMProvider[];
|
|
@@ -63,7 +28,6 @@ export function ai<
|
|
|
63
28
|
providers: config.providers,
|
|
64
29
|
pricing: config.pricing,
|
|
65
30
|
});
|
|
66
|
-
const completionElement = Completion;
|
|
67
31
|
|
|
68
32
|
const Budget = createBudgetAggregate({
|
|
69
33
|
name: config.name,
|
|
@@ -72,25 +36,20 @@ export function ai<
|
|
|
72
36
|
userToken: config.userToken,
|
|
73
37
|
CompletionAggregate: Completion,
|
|
74
38
|
});
|
|
75
|
-
const budgetElement = Budget;
|
|
76
|
-
|
|
77
|
-
const streamRoute = createStreamCompletionRoute({
|
|
78
|
-
name: config.name,
|
|
79
|
-
completionElement,
|
|
80
|
-
userToken: config.userToken,
|
|
81
|
-
providers: config.providers,
|
|
82
|
-
pricing: config.pricing,
|
|
83
|
-
});
|
|
84
39
|
|
|
85
|
-
const elements = [
|
|
40
|
+
const elements: ArcContextElement<any>[] = [Completion, Budget];
|
|
86
41
|
|
|
87
|
-
return
|
|
88
|
-
|
|
42
|
+
return {
|
|
43
|
+
context: context(elements),
|
|
44
|
+
elements,
|
|
89
45
|
completionId,
|
|
90
46
|
budgetId,
|
|
91
47
|
Completion,
|
|
92
48
|
Budget,
|
|
93
|
-
config.providers,
|
|
94
|
-
|
|
95
|
-
|
|
49
|
+
providers: config.providers,
|
|
50
|
+
resolveProvider: (model: string) =>
|
|
51
|
+
config.providers.find((p) => p.models.includes(model)),
|
|
52
|
+
};
|
|
96
53
|
}
|
|
54
|
+
|
|
55
|
+
export type AIConfig = ReturnType<typeof ai>;
|
package/src/index.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
// --- Builder API ---
|
|
2
|
-
export { ai
|
|
2
|
+
export { ai } from "./ai-builder";
|
|
3
|
+
export type { AIConfig } from "./ai-builder";
|
|
3
4
|
|
|
4
5
|
// --- Tool helper ---
|
|
5
6
|
export { tool, ArcTool } from "./tool/tool";
|
|
6
|
-
export type { ArcToolAny, JsonSchemaToolDef } from "./tool/tool";
|
|
7
|
+
export type { ArcToolAny, JsonSchemaToolDef, ToolContext } from "./tool/tool";
|
|
7
8
|
|
|
8
9
|
// --- Aggregate factories & types ---
|
|
9
10
|
export { createCompletionAggregate, createCompletionId } from "./aggregates/completion";
|
|
@@ -11,9 +12,6 @@ export type { CompletionAggregate, CompletionId } from "./aggregates/completion"
|
|
|
11
12
|
export { createBudgetAggregate, createBudgetId } from "./aggregates/budget";
|
|
12
13
|
export type { BudgetAggregate, BudgetId } from "./aggregates/budget";
|
|
13
14
|
|
|
14
|
-
// --- Route ---
|
|
15
|
-
export { createStreamCompletionRoute } from "./routes/stream-completion";
|
|
16
|
-
|
|
17
15
|
// --- Provider types ---
|
|
18
16
|
export type {
|
|
19
17
|
LLMProvider,
|
|
@@ -22,6 +20,8 @@ export type {
|
|
|
22
20
|
CompletionResult,
|
|
23
21
|
StreamChunk,
|
|
24
22
|
StreamEventType,
|
|
23
|
+
ChatStreamEvent,
|
|
24
|
+
ChatStreamEventType,
|
|
25
25
|
Message,
|
|
26
26
|
MessageRole,
|
|
27
27
|
ToolCall,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { route, type ArcAggregateElement } from "@arcote.tech/arc";
|
|
2
2
|
import type { Token } from "@arcote.tech/arc-auth";
|
|
3
|
-
import type { LLMProvider, PricingConfig, StreamChunk } from "../types";
|
|
3
|
+
import type { LLMProvider, Message, PricingConfig, StreamChunk } from "../types";
|
|
4
4
|
import type { ArcToolAny } from "../tool/tool";
|
|
5
5
|
|
|
6
6
|
export type StreamCompletionRouteData = {
|
|
@@ -30,7 +30,7 @@ export const createStreamCompletionRoute = <
|
|
|
30
30
|
POST: async (ctx, req) => {
|
|
31
31
|
const body = await req.json() as {
|
|
32
32
|
model: string;
|
|
33
|
-
messages:
|
|
33
|
+
messages: Message[];
|
|
34
34
|
tools?: { name: string; description: string; parameters: Record<string, unknown> }[];
|
|
35
35
|
webSearch?: boolean;
|
|
36
36
|
};
|
|
@@ -57,7 +57,7 @@ export const createStreamCompletionRoute = <
|
|
|
57
57
|
const result = await provider.streamComplete(
|
|
58
58
|
{
|
|
59
59
|
model: body.model,
|
|
60
|
-
messages: body.messages
|
|
60
|
+
messages: body.messages,
|
|
61
61
|
tools: body.tools,
|
|
62
62
|
webSearch: body.webSearch,
|
|
63
63
|
},
|
package/src/tool/tool.ts
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
ArcFunction,
|
|
3
|
+
defaultFunctionData,
|
|
4
|
+
object,
|
|
5
|
+
type ArcRawShape,
|
|
6
|
+
type $type,
|
|
7
|
+
type ArcContextElement,
|
|
8
|
+
type ArcFunctionData,
|
|
9
|
+
} from "@arcote.tech/arc";
|
|
2
10
|
|
|
3
11
|
// ─── JSON Schema Tool Definition (sent to LLM) ──────────────────
|
|
4
12
|
|
|
@@ -8,6 +16,14 @@ export interface JsonSchemaToolDef {
|
|
|
8
16
|
parameters: Record<string, unknown>;
|
|
9
17
|
}
|
|
10
18
|
|
|
19
|
+
// ─── Tool Context (passed to handle) ────────────────────────────
|
|
20
|
+
|
|
21
|
+
export interface ToolContext {
|
|
22
|
+
mutate: (element: ArcContextElement<any>) => any;
|
|
23
|
+
query: (element: ArcContextElement<any>) => any;
|
|
24
|
+
identifyBy: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
11
27
|
// ─── ArcTool ─────────────────────────────────────────────────────
|
|
12
28
|
|
|
13
29
|
export class ArcTool<
|
|
@@ -15,55 +31,84 @@ export class ArcTool<
|
|
|
15
31
|
const S extends ArcRawShape = ArcRawShape,
|
|
16
32
|
> {
|
|
17
33
|
readonly name: Name;
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#handler?: (params: $type<ReturnType<typeof object<S>>>) => Promise<string>;
|
|
34
|
+
#fn: ArcFunction<any>;
|
|
35
|
+
#handler?: (ctx: ToolContext, params: $type<ReturnType<typeof object<S>>>) => Promise<string>;
|
|
21
36
|
|
|
22
|
-
constructor(name: Name) {
|
|
37
|
+
constructor(name: Name, fn?: ArcFunction<any>) {
|
|
23
38
|
this.name = name;
|
|
39
|
+
this.#fn = fn ?? new ArcFunction(defaultFunctionData);
|
|
24
40
|
}
|
|
25
41
|
|
|
26
42
|
description<const D extends string>(desc: D) {
|
|
27
|
-
this.#description
|
|
28
|
-
|
|
43
|
+
const next = new ArcTool<Name, S>(this.name, this.#fn.description(desc));
|
|
44
|
+
next.#handler = this.#handler;
|
|
45
|
+
return next;
|
|
29
46
|
}
|
|
30
47
|
|
|
31
48
|
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;
|
|
49
|
+
const next = new ArcTool<Name, NewS>(this.name, this.#fn.withParams(shape));
|
|
35
50
|
return next;
|
|
36
51
|
}
|
|
37
52
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
53
|
+
mutate(elements: ArcContextElement<any>[]) {
|
|
54
|
+
const next = new ArcTool<Name, S>(this.name, this.#fn.mutate(elements));
|
|
55
|
+
next.#handler = this.#handler;
|
|
56
|
+
return next;
|
|
41
57
|
}
|
|
42
58
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
59
|
+
handle(handler: (ctx: ToolContext, params: $type<ReturnType<typeof object<S>>>) => Promise<string>) {
|
|
60
|
+
const next = new ArcTool<Name, S>(this.name, this.#fn);
|
|
61
|
+
next.#handler = handler;
|
|
62
|
+
return next;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
get hasHandler(): boolean {
|
|
66
|
+
return !!this.#handler;
|
|
67
|
+
}
|
|
50
68
|
|
|
69
|
+
get isServerTool(): boolean {
|
|
70
|
+
return this.hasHandler;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
get isClientTool(): boolean {
|
|
74
|
+
return !this.hasHandler;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
get mutationElements(): ArcContextElement<any>[] {
|
|
78
|
+
return this.#fn.data.mutationElements || [];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
toJsonSchema(): JsonSchemaToolDef {
|
|
82
|
+
const fnSchema = this.#fn.toJsonSchema();
|
|
51
83
|
return {
|
|
52
84
|
name: this.name,
|
|
53
|
-
description: this.#description,
|
|
54
|
-
parameters:
|
|
85
|
+
description: (this.#fn.data.description as string) || "",
|
|
86
|
+
parameters: fnSchema.params ?? { type: "object", properties: {} },
|
|
55
87
|
};
|
|
56
88
|
}
|
|
57
89
|
|
|
58
|
-
|
|
59
|
-
* Execute the tool handler
|
|
60
|
-
*/
|
|
61
|
-
async execute(params: Record<string, unknown>): Promise<string> {
|
|
90
|
+
async executeWithContext(params: Record<string, unknown>, ctx: ToolContext): Promise<string> {
|
|
62
91
|
if (!this.#handler) {
|
|
63
92
|
throw new Error(`Tool "${this.name}" has no handler`);
|
|
64
93
|
}
|
|
94
|
+
return this.#handler(ctx, params as $type<ReturnType<typeof object<S>>>);
|
|
95
|
+
}
|
|
65
96
|
|
|
66
|
-
|
|
97
|
+
async execute(params: Record<string, unknown>): Promise<string> {
|
|
98
|
+
if (!this.#handler) {
|
|
99
|
+
throw new Error(`Tool "${this.name}" has no handler`);
|
|
100
|
+
}
|
|
101
|
+
if (this.mutationElements.length > 0) {
|
|
102
|
+
throw new Error(
|
|
103
|
+
`Tool "${this.name}" has mutation dependencies — use executeWithContext() instead`,
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
const emptyCtx: ToolContext = {
|
|
107
|
+
mutate: () => { throw new Error("No mutation context available"); },
|
|
108
|
+
query: () => { throw new Error("No query context available"); },
|
|
109
|
+
identifyBy: "",
|
|
110
|
+
};
|
|
111
|
+
return this.#handler(emptyCtx, params as $type<ReturnType<typeof object<S>>>);
|
|
67
112
|
}
|
|
68
113
|
}
|
|
69
114
|
|
package/src/types.ts
CHANGED
|
@@ -51,6 +51,7 @@ export interface CompletionRequest {
|
|
|
51
51
|
webSearch?: boolean;
|
|
52
52
|
temperature?: number;
|
|
53
53
|
maxTokens?: number;
|
|
54
|
+
previousResponseId?: string;
|
|
54
55
|
}
|
|
55
56
|
|
|
56
57
|
export interface CompletionResult {
|
|
@@ -58,6 +59,7 @@ export interface CompletionResult {
|
|
|
58
59
|
toolCalls: ToolCall[];
|
|
59
60
|
usage: TokenUsage;
|
|
60
61
|
finishReason: FinishReason;
|
|
62
|
+
responseId?: string;
|
|
61
63
|
}
|
|
62
64
|
|
|
63
65
|
// ─── Streaming ───────────────────────────────────────────────────
|
|
@@ -92,6 +94,30 @@ export interface LLMProvider {
|
|
|
92
94
|
): Promise<CompletionResult>;
|
|
93
95
|
}
|
|
94
96
|
|
|
97
|
+
// ─── Chat Stream (SSE events for chat streaming) ────────────────
|
|
98
|
+
|
|
99
|
+
export type ChatStreamEventType =
|
|
100
|
+
| "content_delta"
|
|
101
|
+
| "server_tool_start"
|
|
102
|
+
| "server_tool_result"
|
|
103
|
+
| "client_tool_request"
|
|
104
|
+
| "usage_update"
|
|
105
|
+
| "done"
|
|
106
|
+
| "error";
|
|
107
|
+
|
|
108
|
+
export interface ChatStreamEvent {
|
|
109
|
+
type: ChatStreamEventType;
|
|
110
|
+
sessionId: string;
|
|
111
|
+
content?: string;
|
|
112
|
+
toolCall?: ToolCall;
|
|
113
|
+
toolResult?: ToolResult;
|
|
114
|
+
toolCalls?: ToolCall[];
|
|
115
|
+
usage?: TokenUsage;
|
|
116
|
+
finishReason?: FinishReason;
|
|
117
|
+
executionCount?: number;
|
|
118
|
+
error?: string;
|
|
119
|
+
}
|
|
120
|
+
|
|
95
121
|
// ─── Pricing ─────────────────────────────────────────────────────
|
|
96
122
|
|
|
97
123
|
export interface PricingConfig {
|