@arcote.tech/arc-ai 0.4.9 → 0.5.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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@arcote.tech/arc-ai",
3
3
  "type": "module",
4
- "version": "0.4.9",
4
+ "version": "0.5.0",
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.4.9",
14
- "@arcote.tech/arc-auth": "^0.4.9",
13
+ "@arcote.tech/arc": "^0.5.0",
14
+ "@arcote.tech/arc-auth": "^0.5.0",
15
15
  "typescript": "^5.0.0"
16
16
  },
17
17
  "devDependencies": {
@@ -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: ArcAggregateElement;
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 { createCompletionId, createCompletionAggregate } from "./aggregates/completion";
8
- import { createBudgetId, createBudgetAggregate } from "./aggregates/budget";
9
- import { createStreamCompletionRoute } from "./routes/stream-completion";
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 class AIBuilder<
13
- CompId,
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 = [completionElement, budgetElement, streamRoute];
40
+ const elements: ArcContextElement<any>[] = [Completion, Budget];
86
41
 
87
- return new AIBuilder(
88
- config.name,
42
+ return {
43
+ context: context(elements),
44
+ elements,
89
45
  completionId,
90
46
  budgetId,
91
47
  Completion,
92
48
  Budget,
93
- config.providers,
94
- elements,
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, AIBuilder } from "./ai-builder";
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: { role: string; content: string }[];
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 as any,
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 { object, type ArcRawShape, type $type } from "@arcote.tech/arc";
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
- #description: string = "";
19
- #schema?: S;
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 = desc;
28
- return this;
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
- handle(handler: (params: $type<ReturnType<typeof object<S>>>) => Promise<string>) {
39
- this.#handler = handler;
40
- return this;
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
- * 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: {} };
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: parametersSchema,
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
- return this.#handler(params as any);
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 {