@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/src/agent.ts ADDED
@@ -0,0 +1,11 @@
1
+ import { Loop } from "./agent/loop";
2
+
3
+ type Agent = {
4
+ loop: typeof Loop.agentLoop;
5
+ continue: typeof Loop.agentLoopContinue;
6
+ };
7
+
8
+ export const agent = {
9
+ loop: Loop.agentLoop,
10
+ continue: Loop.agentLoopContinue,
11
+ } as Agent;
@@ -0,0 +1,271 @@
1
+ import { type Static, Type } from "@sinclair/typebox";
2
+ import { Agent } from "../agent/agent";
3
+ import { Message } from "../message/message";
4
+
5
+ // TODO sanchitrk:
6
+ // for delta events we can have metadata KV that a provider can use for internal state
7
+ // similar to index what anthropic does
8
+ export namespace Event {
9
+ export const LLMMessageEventType = {
10
+ start: "start",
11
+
12
+ textStart: "text.start",
13
+ textDelta: "text.delta",
14
+ textEnd: "text.end",
15
+
16
+ thinkingStart: "thinking.start",
17
+ thinkingDelta: "thinking.delta",
18
+ thinkingEnd: "thinking.end",
19
+
20
+ toolcallStart: "toolcall.start",
21
+ toolcallDelta: "toolcall.delta",
22
+ toolcallEnd: "toolcall.end",
23
+
24
+ done: "done",
25
+ error: "error",
26
+ } as const;
27
+ export type LLMMessageEventType = (typeof LLMMessageEventType)[keyof typeof LLMMessageEventType];
28
+
29
+ /**
30
+ * Event protocol for AssistantMessageEventStream.
31
+ *
32
+ * Streams should emit `start` before partial updates, then terminate with either:
33
+ * - `done` carrying the final successful AssistantMessage, or
34
+ * - `error` carrying the final AssistantMessage with stopReason "error" or "aborted"
35
+ * and errorMessage.
36
+ */
37
+ export const LLMMessageEventSchema = Type.Union([
38
+ Type.Object({
39
+ type: Type.Literal(LLMMessageEventType.start),
40
+ partial: Message.AssistantMessageSchema,
41
+ }),
42
+ Type.Object({
43
+ type: Type.Literal(LLMMessageEventType.textStart),
44
+ partIndex: Type.Number(),
45
+ partial: Message.AssistantMessageSchema,
46
+ }),
47
+ Type.Object({
48
+ type: Type.Literal(LLMMessageEventType.textDelta),
49
+ partIndex: Type.Number(),
50
+ delta: Type.String(),
51
+ partial: Message.AssistantMessageSchema,
52
+ }),
53
+ Type.Object({
54
+ type: Type.Literal(LLMMessageEventType.textEnd),
55
+ partIndex: Type.Number(),
56
+ content: Type.String(),
57
+ partial: Message.AssistantMessageSchema,
58
+ }),
59
+ Type.Object({
60
+ type: Type.Literal(LLMMessageEventType.thinkingStart),
61
+ partIndex: Type.Number(),
62
+ partial: Message.AssistantMessageSchema,
63
+ }),
64
+ Type.Object({
65
+ type: Type.Literal(LLMMessageEventType.thinkingDelta),
66
+ partIndex: Type.Number(),
67
+ delta: Type.String(),
68
+ partial: Message.AssistantMessageSchema,
69
+ }),
70
+ Type.Object({
71
+ type: Type.Literal(LLMMessageEventType.thinkingEnd),
72
+ partIndex: Type.Number(),
73
+ content: Type.String(),
74
+ partial: Message.AssistantMessageSchema,
75
+ }),
76
+ Type.Object({
77
+ type: Type.Literal(LLMMessageEventType.toolcallStart),
78
+ partIndex: Type.Number(),
79
+ partial: Message.AssistantMessageSchema,
80
+ }),
81
+ Type.Object({
82
+ type: Type.Literal(LLMMessageEventType.toolcallDelta),
83
+ partIndex: Type.Number(),
84
+ delta: Type.String(),
85
+ partial: Message.AssistantMessageSchema,
86
+ }),
87
+ Type.Object({
88
+ type: Type.Literal(LLMMessageEventType.toolcallEnd),
89
+ partIndex: Type.Number(),
90
+ toolCall: Message.ToolCallSchema,
91
+ partial: Message.AssistantMessageSchema,
92
+ }),
93
+ Type.Object({
94
+ type: Type.Literal(LLMMessageEventType.done),
95
+ reason: Type.Union([Type.Literal("stop"), Type.Literal("length"), Type.Literal("toolUse")]),
96
+ message: Message.AssistantMessageSchema,
97
+ }),
98
+ Type.Object({
99
+ type: Type.Literal(LLMMessageEventType.error),
100
+ reason: Type.Union([Type.Literal("aborted"), Type.Literal("error")]),
101
+ error: Message.AssistantMessageSchema,
102
+ }),
103
+ ]);
104
+ export type LLMMessageEvent = Static<typeof LLMMessageEventSchema>;
105
+
106
+ export const AgentEventType = {
107
+ // Agent lifecycle
108
+ agentStart: "agent.start",
109
+ agentEnd: "agent.end",
110
+ // Turn lifecycle - a turn is one assistant response + any tool calls/results
111
+ turnStart: "turn.start",
112
+ turnEnd: "turn.end",
113
+ // Message lifecycle - emitted for user, assistant message and corresponding parts
114
+ messageStart: "message.start",
115
+
116
+ messagePartStart: "message.part.start",
117
+ messagePartUpdate: "message.part.update",
118
+ messagePartEnd: "message.part.end",
119
+
120
+ messageUpdate: "message.update",
121
+ messageEnd: "message.end",
122
+ // Tool execution lifecycle
123
+ toolExecutionStart: "tool.execution.start",
124
+ toolExecutionUpdate: "tool.execution.update",
125
+ toolExecutionEnd: "tool.execution.end",
126
+ } as const;
127
+ export type AgentEventType = (typeof AgentEventType)[keyof typeof AgentEventType];
128
+
129
+ //
130
+ // message parts
131
+ const UserMessagePart = Type.Union([Message.TextContentSchema, Message.ImageContentSchema]);
132
+ const AssistantMessagePart = Type.Union([
133
+ Message.TextContentSchema,
134
+ Message.ImageContentSchema,
135
+ Message.ThinkingContentSchema,
136
+ Message.ToolCallSchema,
137
+ ]);
138
+
139
+ //
140
+ // user message part
141
+ // part start
142
+ const UserMessagePartStart = Type.Object({
143
+ type: Type.Literal(AgentEventType.messagePartStart),
144
+ message: Message.UserMessageSchema,
145
+ partIndex: Type.Number(),
146
+ part: UserMessagePart,
147
+ });
148
+ // part end
149
+ const UserMessagePartEnd = Type.Object({
150
+ type: Type.Literal(AgentEventType.messagePartEnd),
151
+ message: Message.UserMessageSchema,
152
+ partIndex: Type.Number(),
153
+ part: UserMessagePart,
154
+ });
155
+
156
+ //
157
+ // assistant message part
158
+ // part start
159
+ const AssistantMessagePartStart = Type.Object({
160
+ type: Type.Literal(AgentEventType.messagePartStart),
161
+ message: Message.AssistantMessageSchema,
162
+ partIndex: Type.Number(),
163
+ part: AssistantMessagePart,
164
+ });
165
+ // part update llm
166
+ const AssistantMessagePartLLMUpdate = Type.Object({
167
+ type: Type.Literal(AgentEventType.messagePartUpdate),
168
+ message: Message.AssistantMessageSchema,
169
+ partIndex: Type.Number(),
170
+ part: AssistantMessagePart,
171
+ source: Type.Literal("llm"),
172
+ });
173
+ // part update tool
174
+ const AssistantMessagePartToolUpdate = Type.Object({
175
+ type: Type.Literal(AgentEventType.messagePartUpdate),
176
+ message: Message.AssistantMessageSchema,
177
+ partIndex: Type.Number(),
178
+ part: AssistantMessagePart,
179
+ source: Type.Literal("tool"),
180
+ });
181
+ // part end
182
+ const AssistantMessagePartEnd = Type.Object({
183
+ type: Type.Literal(AgentEventType.messagePartEnd),
184
+ message: Message.AssistantMessageSchema,
185
+ partIndex: Type.Number(),
186
+ part: AssistantMessagePart,
187
+ });
188
+
189
+ //
190
+ // tool exection
191
+ const ToolExecutionStartSchema = Type.Composite([
192
+ Agent.ToolCallInFlightSchema,
193
+ Type.Object({
194
+ type: Type.Literal(AgentEventType.toolExecutionStart),
195
+ }),
196
+ ]);
197
+ export type ToolExecutionStart = Static<typeof ToolExecutionStartSchema>;
198
+
199
+ const ToolExecutionUpdateSchema = Type.Composite([
200
+ Agent.ToolCallInFlightSchema,
201
+ Type.Object({
202
+ type: Type.Literal(AgentEventType.toolExecutionUpdate),
203
+ }),
204
+ Message.ToolRunningSchema,
205
+ ]);
206
+ export type ToolExecutionUpdate = Static<typeof ToolExecutionUpdateSchema>;
207
+
208
+ const ToolExecutionEndSchema = Type.Composite([
209
+ Agent.ToolCallInFlightSchema,
210
+ Type.Object({
211
+ type: Type.Literal(AgentEventType.toolExecutionEnd),
212
+ }),
213
+ Type.Union([Message.ToolCompletedSchema, Message.ToolErrorSchema]),
214
+ ]);
215
+ export type ToolExecutionEnd = Static<typeof ToolExecutionEndSchema>;
216
+
217
+ /**
218
+ * Events emitted by the Agent lifecycle
219
+ * These events provide fine-grained lifecycle information for messages, turns, and tool execution.
220
+ */
221
+ export const AgentEventSchema = Type.Union([
222
+ //
223
+ // Agent lifecycle
224
+ Type.Object({
225
+ type: Type.Literal(AgentEventType.agentStart),
226
+ }),
227
+ Type.Object({
228
+ type: Type.Literal(AgentEventType.agentEnd),
229
+ messages: Type.Array(Message.MessageSchema),
230
+ }),
231
+ //
232
+ // Turn lifecycle - a turn is one assistant response + any tool calls/results
233
+ Type.Object({
234
+ type: Type.Literal(AgentEventType.turnStart),
235
+ }),
236
+ Type.Object({
237
+ type: Type.Literal(AgentEventType.turnEnd),
238
+ message: Message.AssistantMessageSchema,
239
+ }),
240
+ //
241
+ // Message lifecycle - emitted for user, assistant and corresponding parts
242
+ Type.Object({
243
+ type: Type.Literal(AgentEventType.messageStart),
244
+ message: Message.MessageSchema,
245
+ }),
246
+ // user message parts lifecycle
247
+ Type.Union([UserMessagePartStart, UserMessagePartEnd]),
248
+ //
249
+ // assistant message parts lifecycle
250
+ Type.Union([
251
+ AssistantMessagePartStart,
252
+ AssistantMessagePartLLMUpdate,
253
+ AssistantMessagePartToolUpdate,
254
+ AssistantMessagePartEnd,
255
+ ]),
256
+ Type.Object({
257
+ type: Type.Literal(AgentEventType.messageUpdate),
258
+ message: Message.MessageSchema,
259
+ }),
260
+ Type.Object({
261
+ type: Type.Literal(AgentEventType.messageEnd),
262
+ message: Message.MessageSchema,
263
+ }),
264
+ //
265
+ // Tool execution lifecycle
266
+ ToolExecutionStartSchema,
267
+ ToolExecutionUpdateSchema,
268
+ ToolExecutionEndSchema,
269
+ ]);
270
+ export type AgentEvent = Static<typeof AgentEventSchema>;
271
+ }
package/src/index.ts ADDED
@@ -0,0 +1,10 @@
1
+ export { Agent } from "./agent/agent";
2
+ export { agent } from "./agent.ts";
3
+ export { Event } from "./event/event";
4
+ export { llm } from "./llm";
5
+ export { Message } from "./message/message";
6
+ export { ModelCatalog } from "./model/catalog";
7
+ export { Model } from "./model/model";
8
+ export { Stream } from "./provider/stream";
9
+ export { complete, completeSimple, stream, streamSimple } from "./stream";
10
+ export { validateToolArguments, validateToolCall } from "./utils/validation";
package/src/llm.ts ADDED
@@ -0,0 +1,29 @@
1
+ import { Model } from "./model/model";
2
+ import type { Provider } from "./provider/provider";
3
+
4
+ type LLM = {
5
+ <TProvider extends Provider.KnownProvider, TModel extends Model.Value["id"]>(
6
+ provider: TProvider,
7
+ model: TModel,
8
+ ): Promise<Model.Value | undefined>;
9
+ model: typeof Model.getModel;
10
+ models: typeof Model.getModels;
11
+ providers: typeof Model.getProviders;
12
+ registry: typeof Model.registry;
13
+ supportsXhigh: typeof Model.supportsXhigh;
14
+ modelsAreEqual: typeof Model.modelsAreEqual;
15
+ };
16
+
17
+ const llmImpl = async <TProvider extends Provider.KnownProvider, TModel extends Model.Value["id"]>(
18
+ provider: TProvider,
19
+ model: TModel,
20
+ ): Promise<Model.Value | undefined> => Model.getModel(provider, model);
21
+
22
+ export const llm = Object.assign(llmImpl, {
23
+ model: Model.getModel,
24
+ models: Model.getModels,
25
+ providers: Model.getProviders,
26
+ registry: Model.registry,
27
+ supportsXhigh: Model.supportsXhigh,
28
+ modelsAreEqual: Model.modelsAreEqual,
29
+ }) as LLM;
@@ -0,0 +1,280 @@
1
+ import { type Static, type TSchema, Type } from "@sinclair/typebox";
2
+ import { Model } from "../model/model";
3
+ import { Provider } from "../provider/provider";
4
+
5
+ export namespace Message {
6
+ export const TextContentSchema = Type.Object({
7
+ type: Type.Literal("text"),
8
+ text: Type.String(),
9
+ textSignature: Type.Optional(Type.String()), // e.g., for OpenAI responses, the message ID
10
+ });
11
+ export type TextContent = Static<typeof TextContentSchema>;
12
+
13
+ export const ImageContentSchema = Type.Object({
14
+ type: Type.Literal("image"),
15
+ data: Type.String(), // base64 encoded image data
16
+ mimeType: Type.String(), // e.g., "image/jpeg", "image/png"
17
+ });
18
+ export type ImageContent = Static<typeof ImageContentSchema>;
19
+
20
+ export const ThinkingContentSchema = Type.Object({
21
+ type: Type.Literal("thinking"),
22
+ thinking: Type.String(),
23
+ thinkingSignature: Type.Optional(Type.String()), // e.g., for OpenAI responses, the reasoning item ID
24
+ /** When true, the thinking content was redacted by safety filters. The opaque
25
+ * encrypted payload is stored in `thinkingSignature` so it can be passed back
26
+ * to the API for multi-turn continuity. */
27
+ redacted: Type.Optional(Type.Boolean()),
28
+ });
29
+ export type ThinkingContent = Static<typeof ThinkingContentSchema>;
30
+
31
+ export const ToolCallBaseSchema = Type.Object({
32
+ type: Type.Literal("toolCall"),
33
+ callID: Type.String(),
34
+ name: Type.String(),
35
+ arguments: Type.Record(Type.String(), Type.Any()),
36
+ thoughtSignature: Type.Optional(Type.String()), // Google-specific: opaque signature for reusing thought context
37
+ time: Type.Object({
38
+ start: Type.Number(), // Unix timestamp in milliseconds
39
+ end: Type.Number(), // Unix timestamp in milliseconds, last known lifecycle update
40
+ }),
41
+ });
42
+
43
+ export const ToolSuccessResult = Type.Object({
44
+ content: Type.Array(Type.Union([TextContentSchema, ImageContentSchema])),
45
+ details: Type.Optional(Type.Any()),
46
+ isError: Type.Literal(false),
47
+ });
48
+ export const ToolErrorResult = Type.Object({
49
+ content: Type.Array(Type.Union([TextContentSchema, ImageContentSchema])),
50
+ details: Type.Optional(Type.Any()),
51
+ isError: Type.Literal(true),
52
+ });
53
+ export const ToolRunningPartial = Type.Object({
54
+ content: Type.Optional(Type.Array(Type.Union([TextContentSchema, ImageContentSchema]))),
55
+ details: Type.Optional(Type.Any()),
56
+ });
57
+
58
+ export const ToolStatusEnum = {
59
+ pending: "pending",
60
+ running: "running",
61
+ completed: "completed",
62
+ error: "error",
63
+ skipped: "skipped",
64
+ aborted: "aborted",
65
+ } as const;
66
+
67
+ export const ToolRunningSchema = Type.Object({
68
+ status: Type.Literal(ToolStatusEnum.running),
69
+ partial: Type.Optional(ToolRunningPartial),
70
+ });
71
+ export const ToolCompletedSchema = Type.Object({
72
+ status: Type.Literal(ToolStatusEnum.completed),
73
+ result: ToolSuccessResult,
74
+ });
75
+ export const ToolErrorSchema = Type.Object({
76
+ status: Type.Literal(ToolStatusEnum.error),
77
+ result: ToolErrorResult,
78
+ });
79
+ export const ToolSkippedSchema = Type.Object({
80
+ status: Type.Literal(ToolStatusEnum.skipped),
81
+ result: ToolErrorResult,
82
+ });
83
+ export const ToolAbortedSchema = Type.Object({
84
+ status: Type.Literal(ToolStatusEnum.aborted),
85
+ result: ToolErrorResult,
86
+ });
87
+
88
+ const ToolCallPendingPart = Type.Intersect([
89
+ ToolCallBaseSchema,
90
+ Type.Object({
91
+ status: Type.Literal(ToolStatusEnum.pending),
92
+ }),
93
+ ]);
94
+ const ToolCallRunningPart = Type.Composite([ToolCallBaseSchema, ToolRunningSchema]);
95
+ const ToolCallCompletedPart = Type.Composite([ToolCallBaseSchema, ToolCompletedSchema]);
96
+ const ToolCallErrorPart = Type.Composite([ToolCallBaseSchema, ToolErrorSchema]);
97
+ const ToolCallSkippedPart = Type.Composite([ToolCallBaseSchema, ToolSkippedSchema]);
98
+ const ToolCallAbortedPart = Type.Composite([ToolCallBaseSchema, ToolAbortedSchema]);
99
+
100
+ export const ToolCallSchema = Type.Union([
101
+ ToolCallPendingPart,
102
+ ToolCallRunningPart,
103
+ ToolCallCompletedPart,
104
+ ToolCallErrorPart,
105
+ ToolCallSkippedPart,
106
+ ToolCallAbortedPart,
107
+ ]);
108
+
109
+ export type ToolCall = Static<typeof ToolCallSchema>;
110
+ export type ToolCallPendingPart = Static<typeof ToolCallPendingPart>;
111
+ export type ToolCallRunningPart = Static<typeof ToolCallRunningPart>;
112
+ export type ToolCallCompletedPart = Static<typeof ToolCallCompletedPart>;
113
+ export type ToolCallErrorPart = Static<typeof ToolCallErrorPart>;
114
+
115
+ export const UsageSchema = Type.Object({
116
+ input: Type.Number(),
117
+ output: Type.Number(),
118
+ cacheRead: Type.Number(),
119
+ cacheWrite: Type.Number(),
120
+ totalTokens: Type.Number(),
121
+ cost: Type.Object({
122
+ input: Type.Number(),
123
+ output: Type.Number(),
124
+ cacheRead: Type.Number(),
125
+ cacheWrite: Type.Number(),
126
+ total: Type.Number(),
127
+ }),
128
+ });
129
+ export type Usage = Static<typeof UsageSchema>;
130
+
131
+ export const StopReasonSchema = Type.Union([
132
+ Type.Literal("stop"),
133
+ Type.Literal("length"),
134
+ Type.Literal("toolUse"),
135
+ Type.Literal("error"),
136
+ Type.Literal("aborted"),
137
+ ]);
138
+ export type StopReason = Static<typeof StopReasonSchema>;
139
+
140
+ export const UserMessageSchema = Type.Object({
141
+ role: Type.Literal("user"),
142
+ time: Type.Object({
143
+ created: Type.Number(),
144
+ }),
145
+ parts: Type.Array(Type.Union([TextContentSchema, ImageContentSchema])),
146
+ });
147
+ export type UserMessage = Static<typeof UserMessageSchema>;
148
+
149
+ export const AssistantMessageSchema = Type.Object({
150
+ role: Type.Literal("assistant"),
151
+ protocol: Model.KnownProtocolSchema,
152
+ provider: Provider.Info,
153
+ model: Type.String(),
154
+ usage: UsageSchema,
155
+ stopReason: StopReasonSchema,
156
+ errorMessage: Type.Optional(Type.String()),
157
+ time: Type.Object({
158
+ created: Type.Number(),
159
+ completed: Type.Number(),
160
+ }),
161
+ parts: Type.Array(Type.Union([TextContentSchema, ImageContentSchema, ThinkingContentSchema, ToolCallSchema])),
162
+ responseID: Type.Optional(Type.String()), // Provider-specific response/message identifier when the upstream API exposes one
163
+ });
164
+ export type AssistantMessage = Static<typeof AssistantMessageSchema>;
165
+
166
+ export const MessageSchema = Type.Union([UserMessageSchema, AssistantMessageSchema]);
167
+ export type Message = Static<typeof MessageSchema>;
168
+
169
+ /**
170
+ * Generic tool definition with typed parameter schema.
171
+ * Usage:
172
+ *
173
+ * function defineTool<T extends TSchema>(tool: Tool<T>): Tool<T> {
174
+ * return tool;
175
+ * }
176
+ *
177
+ * const search = defineTool({
178
+ * name: "search",
179
+ * description: "Search documents",
180
+ * parameters: Type.Object({
181
+ * query: Type.String(),
182
+ * limit: Type.Optional(Type.Number()),
183
+ * }),
184
+ * });
185
+ *
186
+ * type SearchParams = Static<typeof search.parameters>;
187
+ */
188
+ export const ToolSchema = Type.Object({
189
+ name: Type.String(),
190
+ description: Type.String(),
191
+ parameters: Type.Unsafe<TSchema>(),
192
+ });
193
+ export interface Tool<TParameters extends TSchema = TSchema> {
194
+ name: string;
195
+ description: string;
196
+ parameters: TParameters;
197
+ }
198
+ export type ToolArguments<T extends Tool> = Static<T["parameters"]>;
199
+
200
+ export const ContextSchema = Type.Object({
201
+ systemPrompt: Type.Optional(Type.String()),
202
+ messages: Type.Array(MessageSchema),
203
+ tools: Type.Optional(Type.Array(ToolSchema)),
204
+ });
205
+ export type Context = Static<typeof ContextSchema>;
206
+
207
+ export function transformMessages<TProtocol extends Model.KnownProtocol>(
208
+ messages: Message[],
209
+ model: Model.TModel<TProtocol>,
210
+ normalizeToolCallId?: (id: string, model: Model.TModel<TProtocol>, source: AssistantMessage) => string,
211
+ ): Message[] {
212
+ const toolCallIDMap = new Map<string, string>();
213
+
214
+ const transformed: Message[] = [];
215
+ for (const msg of messages) {
216
+ if (msg.role === "user") {
217
+ transformed.push(msg);
218
+ continue;
219
+ }
220
+
221
+ const assistantMsg = msg as AssistantMessage;
222
+ const isSameModel =
223
+ assistantMsg.provider.id === model.provider.id &&
224
+ assistantMsg.protocol === model.protocol &&
225
+ assistantMsg.model === model.id;
226
+
227
+ const parts = assistantMsg.parts.flatMap((block) => {
228
+ if (block.type === "thinking") {
229
+ if (isSameModel && block.thinkingSignature) return block;
230
+ if (!block.thinking || block.thinking.trim() === "") return [];
231
+ if (isSameModel) return block;
232
+ return {
233
+ type: "text" as const,
234
+ text: block.thinking,
235
+ };
236
+ }
237
+
238
+ if (block.type === "text") {
239
+ if (isSameModel) return block;
240
+ return {
241
+ type: "text" as const,
242
+ text: block.text,
243
+ };
244
+ }
245
+
246
+ if (block.type === "toolCall") {
247
+ const toolCall = block as ToolCall;
248
+ let normalizedToolCall: ToolCall = toolCall;
249
+
250
+ if (!isSameModel && toolCall.thoughtSignature) {
251
+ normalizedToolCall = { ...toolCall };
252
+ delete (normalizedToolCall as { thoughtSignature?: string }).thoughtSignature;
253
+ }
254
+
255
+ const normalizedID = toolCallIDMap.get(block.callID);
256
+ if (normalizedID && normalizedID !== block.callID) {
257
+ normalizedToolCall = { ...normalizedToolCall, callID: normalizedID };
258
+ } else if (!isSameModel && normalizeToolCallId) {
259
+ const nextID = normalizeToolCallId(toolCall.callID, model, assistantMsg);
260
+ if (nextID !== toolCall.callID) {
261
+ toolCallIDMap.set(toolCall.callID, nextID);
262
+ normalizedToolCall = { ...normalizedToolCall, callID: nextID };
263
+ }
264
+ }
265
+
266
+ return normalizedToolCall;
267
+ }
268
+
269
+ return block;
270
+ });
271
+
272
+ transformed.push({
273
+ ...assistantMsg,
274
+ parts,
275
+ });
276
+ }
277
+
278
+ return transformed;
279
+ }
280
+ }
@@ -0,0 +1,95 @@
1
+ import { Filesystem, lazy } from "@codeworksh/utils";
2
+
3
+ export namespace ModelCatalog {
4
+ // Doesn't represent the full spec from models.dev.
5
+ // Represents the provider entry on a best-effort basis.
6
+ export interface ModelsDevModel {
7
+ id: string;
8
+ name: string;
9
+ family: string;
10
+ attachment: boolean;
11
+ reasoning?: boolean;
12
+ tool_call?: boolean;
13
+ temperature?: boolean;
14
+ knowledge?: string | boolean;
15
+ release_date: string;
16
+ last_updated: string;
17
+ modalities: {
18
+ input?: string[];
19
+ output: string[];
20
+ };
21
+ open_weights: boolean;
22
+ baseUrl?: string;
23
+ headers?: Record<string, string>;
24
+ cost?: {
25
+ input?: number;
26
+ output?: number;
27
+ cache_read?: number;
28
+ cache_write?: number;
29
+ };
30
+ limit?: {
31
+ context?: number;
32
+ output?: number;
33
+ };
34
+ }
35
+
36
+ export interface ModelsDevProvider {
37
+ id: string;
38
+ env: string[];
39
+ npm?: string;
40
+ api: string;
41
+ baseUrl?: string;
42
+ name: string;
43
+ doc?: string;
44
+ key?: string;
45
+ headers?: Record<string, string>;
46
+ models: Record<string, ModelsDevModel>;
47
+ }
48
+ export type ModelsDevCatalog = Record<string, ModelsDevProvider>;
49
+ type LazyModelsDevCatalog = ReturnType<typeof lazy<Promise<ModelsDevCatalog>>>;
50
+
51
+ function modelsDevURL(): string {
52
+ return process.env.OPENCODE_MODELS_URL || "https://models.dev";
53
+ }
54
+
55
+ function modelsDevPath(): string | undefined {
56
+ return process.env.CODEWORK_AIKIT_MODELS_PATH;
57
+ }
58
+
59
+ export const modelsDevData: LazyModelsDevCatalog = lazy(async () => {
60
+ const path = modelsDevPath();
61
+ if (path) {
62
+ const result = await Filesystem.readJson<ModelsDevCatalog>(path).catch(() => undefined);
63
+ if (result) return result;
64
+ }
65
+
66
+ const json = await fetch(`${modelsDevURL()}/api.json`).then((x) => x.text());
67
+ return JSON.parse(json) as ModelsDevCatalog;
68
+ });
69
+
70
+ export async function get(): Promise<ModelsDevCatalog> {
71
+ return modelsDevData();
72
+ }
73
+
74
+ export async function refresh(): Promise<void> {
75
+ const result = await fetch(`${modelsDevURL()}/api.json`, {
76
+ signal: AbortSignal.timeout(10 * 1000),
77
+ }).catch(() => undefined);
78
+ if (!result?.ok) return;
79
+
80
+ const json = await result.text();
81
+ const path = modelsDevPath();
82
+ if (path) {
83
+ await Filesystem.write(path, json);
84
+ }
85
+ modelsDevData.reset();
86
+ }
87
+
88
+ ModelCatalog.refresh();
89
+ setInterval(
90
+ async () => {
91
+ await ModelCatalog.refresh();
92
+ },
93
+ 60 * 1000 * 60,
94
+ ).unref();
95
+ }