@codeworksh/aikit 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 +347 -0
- package/package.json +60 -0
- package/src/agent/agent.ts +75 -0
- package/src/agent/loop.ts +786 -0
- package/src/agent.ts +11 -0
- package/src/event/event.ts +271 -0
- package/src/index.ts +10 -0
- package/src/llm.ts +29 -0
- package/src/message/message.ts +280 -0
- package/src/model/catalog.ts +95 -0
- package/src/model/model.ts +253 -0
- package/src/provider/provider.ts +43 -0
- package/src/provider/providers/anthropic/index.ts +769 -0
- package/src/provider/register.ts +14 -0
- package/src/provider/stream.ts +299 -0
- package/src/stream.ts +72 -0
- package/src/utils/eventstream.ts +95 -0
- package/src/utils/helpers.ts +24 -0
- package/src/utils/jsonparse.ts +32 -0
- package/src/utils/overflow.ts +121 -0
- package/src/utils/sanitize.ts +25 -0
- package/src/utils/validation.ts +63 -0
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import { lazy } from "@codeworksh/utils";
|
|
2
|
+
import { type Static, Type } from "@sinclair/typebox";
|
|
3
|
+
import { keys, mapValues, pick, pipe } from "remeda";
|
|
4
|
+
import { Provider } from "../provider/provider";
|
|
5
|
+
import { ModelCatalog } from "./catalog";
|
|
6
|
+
|
|
7
|
+
export namespace Model {
|
|
8
|
+
export const KnownProtocolEnum = {
|
|
9
|
+
anthropicMessages: "anthropic-messages",
|
|
10
|
+
openaiCompletions: "openai-completions",
|
|
11
|
+
openaiResponses: "openai-responses",
|
|
12
|
+
} as const;
|
|
13
|
+
export const KnownProtocolSchema = Type.Union([
|
|
14
|
+
Type.Literal(KnownProtocolEnum.anthropicMessages),
|
|
15
|
+
Type.Literal(KnownProtocolEnum.openaiCompletions),
|
|
16
|
+
Type.Literal(KnownProtocolEnum.openaiResponses),
|
|
17
|
+
]);
|
|
18
|
+
export type KnownProtocol = Static<typeof KnownProtocolSchema>;
|
|
19
|
+
|
|
20
|
+
const InputSchema = Type.Array(Type.Union([Type.Literal("text"), Type.Literal("image")]));
|
|
21
|
+
const CostSchema = Type.Object({
|
|
22
|
+
input: Type.Number(),
|
|
23
|
+
output: Type.Number(),
|
|
24
|
+
cacheRead: Type.Number(),
|
|
25
|
+
cacheWrite: Type.Number(),
|
|
26
|
+
});
|
|
27
|
+
const HeadersSchema = Type.Optional(Type.Record(Type.String(), Type.String()));
|
|
28
|
+
export const BaseSchema = Type.Object({
|
|
29
|
+
id: Type.String(),
|
|
30
|
+
name: Type.String(),
|
|
31
|
+
provider: Provider.Info,
|
|
32
|
+
baseUrl: Type.String(),
|
|
33
|
+
reasoning: Type.Boolean(),
|
|
34
|
+
input: InputSchema,
|
|
35
|
+
cost: CostSchema,
|
|
36
|
+
contextWindow: Type.Number(),
|
|
37
|
+
maxTokens: Type.Number(),
|
|
38
|
+
headers: HeadersSchema,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
export const OpenAICompletionsCompatSchema = Type.Object({
|
|
42
|
+
supportsStore: Type.Optional(Type.Boolean()),
|
|
43
|
+
supportsDeveloperRole: Type.Optional(Type.Boolean()),
|
|
44
|
+
supportsReasoningEffort: Type.Optional(Type.Boolean()),
|
|
45
|
+
reasoningEffortMap: Type.Optional(Provider.ReasoningEffortMapSchema),
|
|
46
|
+
supportsUsageInStreaming: Type.Optional(Type.Boolean()),
|
|
47
|
+
maxTokensField: Type.Optional(Type.Union([Type.Literal("max_completion_tokens"), Type.Literal("max_tokens")])),
|
|
48
|
+
requiresToolResultName: Type.Optional(Type.Boolean()),
|
|
49
|
+
requiresAssistantAfterToolResult: Type.Optional(Type.Boolean()),
|
|
50
|
+
requiresThinkingAsText: Type.Optional(Type.Boolean()),
|
|
51
|
+
thinkingFormat: Type.Optional(
|
|
52
|
+
Type.Union([
|
|
53
|
+
Type.Literal("openai"),
|
|
54
|
+
Type.Literal("openrouter"),
|
|
55
|
+
Type.Literal("zai"),
|
|
56
|
+
Type.Literal("qwen"),
|
|
57
|
+
Type.Literal("qwen-chat-template"),
|
|
58
|
+
]),
|
|
59
|
+
),
|
|
60
|
+
openRouterRouting: Type.Optional(Provider.OpenRouterRoutingSchema),
|
|
61
|
+
vercelGatewayRouting: Type.Optional(Provider.VercelGatewayRoutingSchema),
|
|
62
|
+
supportsStrictMode: Type.Optional(Type.Boolean()),
|
|
63
|
+
});
|
|
64
|
+
export type OpenAICompletionsCompat = Static<typeof OpenAICompletionsCompatSchema>;
|
|
65
|
+
|
|
66
|
+
export const OpenAIResponsesCompatSchema = Type.Object({});
|
|
67
|
+
export type OpenAIResponsesCompat = Static<typeof OpenAIResponsesCompatSchema>;
|
|
68
|
+
|
|
69
|
+
export const OpenAICompatSchema = Type.Union([OpenAICompletionsCompatSchema, OpenAIResponsesCompatSchema]);
|
|
70
|
+
|
|
71
|
+
export const AnthropicSchema = Type.Composite([
|
|
72
|
+
BaseSchema,
|
|
73
|
+
Type.Object({
|
|
74
|
+
protocol: Type.Literal(KnownProtocolEnum.anthropicMessages),
|
|
75
|
+
}),
|
|
76
|
+
]);
|
|
77
|
+
|
|
78
|
+
export const OpenAICompletionsSchema = Type.Composite([
|
|
79
|
+
BaseSchema,
|
|
80
|
+
Type.Object({
|
|
81
|
+
protocol: Type.Literal(KnownProtocolEnum.openaiCompletions),
|
|
82
|
+
compat: Type.Optional(OpenAICompletionsCompatSchema),
|
|
83
|
+
}),
|
|
84
|
+
]);
|
|
85
|
+
export const OpenAIResponsesSchema = Type.Composite([
|
|
86
|
+
BaseSchema,
|
|
87
|
+
Type.Object({
|
|
88
|
+
protocol: Type.Literal(KnownProtocolEnum.openaiResponses),
|
|
89
|
+
compat: Type.Optional(OpenAIResponsesCompatSchema),
|
|
90
|
+
}),
|
|
91
|
+
]);
|
|
92
|
+
|
|
93
|
+
export const Schema = Type.Union([AnthropicSchema, OpenAICompletionsSchema, OpenAIResponsesSchema]);
|
|
94
|
+
export type Value = Static<typeof Schema>;
|
|
95
|
+
export type TModel<TProtocol extends KnownProtocol> = Extract<Value, { protocol: TProtocol }>;
|
|
96
|
+
const BUILTINS = keys(Provider.KnownProviderEnum) as Provider.KnownProvider[];
|
|
97
|
+
|
|
98
|
+
export function calculateCost(
|
|
99
|
+
model: Value,
|
|
100
|
+
usage: {
|
|
101
|
+
input: number;
|
|
102
|
+
output: number;
|
|
103
|
+
cacheRead: number;
|
|
104
|
+
cacheWrite: number;
|
|
105
|
+
cost: {
|
|
106
|
+
input: number;
|
|
107
|
+
output: number;
|
|
108
|
+
cacheRead: number;
|
|
109
|
+
cacheWrite: number;
|
|
110
|
+
total: number;
|
|
111
|
+
};
|
|
112
|
+
},
|
|
113
|
+
): void {
|
|
114
|
+
usage.cost.input = (usage.input / 1_000_000) * model.cost.input;
|
|
115
|
+
usage.cost.output = (usage.output / 1_000_000) * model.cost.output;
|
|
116
|
+
usage.cost.cacheRead = (usage.cacheRead / 1_000_000) * model.cost.cacheRead;
|
|
117
|
+
usage.cost.cacheWrite = (usage.cacheWrite / 1_000_000) * model.cost.cacheWrite;
|
|
118
|
+
usage.cost.total = usage.cost.input + usage.cost.output + usage.cost.cacheRead + usage.cost.cacheWrite;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export type BuiltInModels = Partial<Record<Provider.KnownProvider, Record<string, Value>>>;
|
|
122
|
+
|
|
123
|
+
function resolveProtocol(providerId: Provider.KnownProvider): KnownProtocol {
|
|
124
|
+
switch (providerId) {
|
|
125
|
+
case Provider.KnownProviderEnum.anthropic:
|
|
126
|
+
return KnownProtocolEnum.anthropicMessages;
|
|
127
|
+
case Provider.KnownProviderEnum.openai:
|
|
128
|
+
return KnownProtocolEnum.openaiResponses;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function normalizeInput(input?: string[]): Array<"text" | "image"> {
|
|
133
|
+
const normalized = new Set<"text" | "image">();
|
|
134
|
+
for (const modality of input ?? ["text"]) {
|
|
135
|
+
if (modality === "text" || modality === "image") {
|
|
136
|
+
normalized.add(modality);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (normalized.size === 0) normalized.add("text");
|
|
140
|
+
return [...normalized];
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function toProviderInfo(
|
|
144
|
+
providerId: Provider.KnownProvider,
|
|
145
|
+
provider: ModelCatalog.ModelsDevProvider,
|
|
146
|
+
): Provider.Info {
|
|
147
|
+
return {
|
|
148
|
+
id: providerId,
|
|
149
|
+
name: provider.name,
|
|
150
|
+
env: provider.env,
|
|
151
|
+
key: provider.key,
|
|
152
|
+
options: provider.headers,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function toModelValue(
|
|
157
|
+
providerId: Provider.KnownProvider,
|
|
158
|
+
provider: ModelCatalog.ModelsDevProvider,
|
|
159
|
+
model: ModelCatalog.ModelsDevModel,
|
|
160
|
+
): Value {
|
|
161
|
+
const baseUrl = model.baseUrl ?? provider.baseUrl ?? provider.api;
|
|
162
|
+
const normalized: Value = {
|
|
163
|
+
id: model.id,
|
|
164
|
+
name: model.name,
|
|
165
|
+
provider: toProviderInfo(providerId, provider),
|
|
166
|
+
baseUrl,
|
|
167
|
+
reasoning: Boolean(model.reasoning),
|
|
168
|
+
input: normalizeInput(model.modalities.input),
|
|
169
|
+
cost: {
|
|
170
|
+
input: model.cost?.input ?? 0,
|
|
171
|
+
output: model.cost?.output ?? 0,
|
|
172
|
+
cacheRead: model.cost?.cache_read ?? 0,
|
|
173
|
+
cacheWrite: model.cost?.cache_write ?? 0,
|
|
174
|
+
},
|
|
175
|
+
contextWindow: model.limit?.context ?? 0,
|
|
176
|
+
maxTokens: model.limit?.output ?? 0,
|
|
177
|
+
headers: model.headers ?? provider.headers,
|
|
178
|
+
protocol: resolveProtocol(providerId),
|
|
179
|
+
};
|
|
180
|
+
return normalized;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export async function getBuiltInModels(): Promise<BuiltInModels> {
|
|
184
|
+
const catalog = await ModelCatalog.get();
|
|
185
|
+
return pipe(
|
|
186
|
+
catalog,
|
|
187
|
+
pick(BUILTINS),
|
|
188
|
+
mapValues((provider, providerId) =>
|
|
189
|
+
mapValues(provider.models, (model) => toModelValue(providerId as Provider.KnownProvider, provider, model)),
|
|
190
|
+
),
|
|
191
|
+
) as BuiltInModels;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export const registry = lazy(async () => {
|
|
195
|
+
const registry: Map<Provider.KnownProvider, Map<string, Value>> = new Map();
|
|
196
|
+
const models = await getBuiltInModels();
|
|
197
|
+
for (const [provider, value] of Object.entries(models) as Array<
|
|
198
|
+
[Provider.KnownProvider, Record<string, Value>]
|
|
199
|
+
>) {
|
|
200
|
+
const providerModels = new Map<string, Value>();
|
|
201
|
+
for (const [id, model] of Object.entries(value)) {
|
|
202
|
+
providerModels.set(id, model);
|
|
203
|
+
}
|
|
204
|
+
registry.set(provider, providerModels);
|
|
205
|
+
}
|
|
206
|
+
return registry;
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
export async function getModel<TProvider extends Provider.KnownProvider, TModel extends Value["id"]>(
|
|
210
|
+
provider: TProvider,
|
|
211
|
+
model: TModel,
|
|
212
|
+
) {
|
|
213
|
+
const data = await registry();
|
|
214
|
+
const providerModels = data.get(provider);
|
|
215
|
+
return providerModels?.get(model);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export async function getProviders(): Promise<Provider.KnownProvider[]> {
|
|
219
|
+
const data = await registry();
|
|
220
|
+
return Array.from(data.keys());
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export async function getModels<TProvider extends Provider.KnownProvider>(provider: TProvider): Promise<Value[]> {
|
|
224
|
+
const data = await registry();
|
|
225
|
+
const models = data.get(provider);
|
|
226
|
+
return models ? Array.from(models.values()) : [];
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function isGpt5OrLater(modelID: string): boolean {
|
|
230
|
+
const match = /^gpt-(\d+)/.exec(modelID);
|
|
231
|
+
if (!match) {
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
234
|
+
return Number(match[1]) >= 5;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Check if a model supports xhigh thinking level.
|
|
239
|
+
* Currently only certain OpenAI Codex models support this.
|
|
240
|
+
*/
|
|
241
|
+
export function supportsXhigh(model: Value): boolean {
|
|
242
|
+
return isGpt5OrLater(model.id);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Check if two models are equal by comparing both their id and provider id.
|
|
247
|
+
* Returns false if either model is null or undefined.
|
|
248
|
+
*/
|
|
249
|
+
export function modelsAreEqual(a: Value | null | undefined, b: Value | null | undefined): boolean {
|
|
250
|
+
if (!a || !b) return false;
|
|
251
|
+
return a.id === b.id && a.provider.id === b.provider.id;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { type Static, Type } from "@sinclair/typebox";
|
|
2
|
+
|
|
3
|
+
export namespace Provider {
|
|
4
|
+
export const KnownProviderEnum = {
|
|
5
|
+
anthropic: "anthropic",
|
|
6
|
+
openai: "openai",
|
|
7
|
+
} as const;
|
|
8
|
+
export const KnownProviderSchema = Type.Union([
|
|
9
|
+
Type.Literal(KnownProviderEnum.anthropic),
|
|
10
|
+
Type.Literal(KnownProviderEnum.openai),
|
|
11
|
+
]);
|
|
12
|
+
export type KnownProvider = Static<typeof KnownProviderSchema>;
|
|
13
|
+
|
|
14
|
+
export const Info = Type.Object({
|
|
15
|
+
id: KnownProviderSchema,
|
|
16
|
+
name: Type.String(),
|
|
17
|
+
env: Type.Array(Type.String()),
|
|
18
|
+
key: Type.Optional(Type.String()),
|
|
19
|
+
options: Type.Optional(Type.Record(Type.String(), Type.Any())),
|
|
20
|
+
});
|
|
21
|
+
export type Info = Static<typeof Info>;
|
|
22
|
+
|
|
23
|
+
export const ReasoningEffortMapSchema = Type.Object({
|
|
24
|
+
minimal: Type.Optional(Type.String()),
|
|
25
|
+
low: Type.Optional(Type.String()),
|
|
26
|
+
medium: Type.Optional(Type.String()),
|
|
27
|
+
high: Type.Optional(Type.String()),
|
|
28
|
+
xhigh: Type.Optional(Type.String()),
|
|
29
|
+
});
|
|
30
|
+
export type ReasoningEffortMap = Static<typeof ReasoningEffortMapSchema>;
|
|
31
|
+
|
|
32
|
+
export const OpenRouterRoutingSchema = Type.Object({
|
|
33
|
+
only: Type.Optional(Type.Array(Type.String())),
|
|
34
|
+
order: Type.Optional(Type.Array(Type.String())),
|
|
35
|
+
});
|
|
36
|
+
export type OpenRouterRouting = Static<typeof OpenRouterRoutingSchema>;
|
|
37
|
+
|
|
38
|
+
export const VercelGatewayRoutingSchema = Type.Object({
|
|
39
|
+
only: Type.Optional(Type.Array(Type.String())),
|
|
40
|
+
order: Type.Optional(Type.Array(Type.String())),
|
|
41
|
+
});
|
|
42
|
+
export type VercelGatewayRouting = Static<typeof VercelGatewayRoutingSchema>;
|
|
43
|
+
}
|