@gajae-code/agent-core 0.1.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.
Files changed (55) hide show
  1. package/CHANGELOG.md +482 -0
  2. package/README.md +473 -0
  3. package/dist/types/agent-loop.d.ts +55 -0
  4. package/dist/types/agent.d.ts +334 -0
  5. package/dist/types/append-only-context.d.ts +113 -0
  6. package/dist/types/compaction/branch-summarization.d.ts +94 -0
  7. package/dist/types/compaction/compaction.d.ts +166 -0
  8. package/dist/types/compaction/entries.d.ts +103 -0
  9. package/dist/types/compaction/errors.d.ts +26 -0
  10. package/dist/types/compaction/index.d.ts +11 -0
  11. package/dist/types/compaction/messages.d.ts +61 -0
  12. package/dist/types/compaction/openai.d.ts +58 -0
  13. package/dist/types/compaction/pruning.d.ts +18 -0
  14. package/dist/types/compaction/utils.d.ts +32 -0
  15. package/dist/types/compaction.d.ts +1 -0
  16. package/dist/types/harmony-leak.d.ts +99 -0
  17. package/dist/types/index.d.ts +10 -0
  18. package/dist/types/proxy.d.ts +84 -0
  19. package/dist/types/run-collector.d.ts +196 -0
  20. package/dist/types/telemetry.d.ts +588 -0
  21. package/dist/types/thinking.d.ts +17 -0
  22. package/dist/types/types.d.ts +407 -0
  23. package/package.json +75 -0
  24. package/src/agent-loop.ts +1279 -0
  25. package/src/agent.ts +1399 -0
  26. package/src/append-only-context.ts +297 -0
  27. package/src/compaction/branch-summarization.ts +339 -0
  28. package/src/compaction/compaction.ts +1065 -0
  29. package/src/compaction/entries.ts +133 -0
  30. package/src/compaction/errors.ts +31 -0
  31. package/src/compaction/index.ts +12 -0
  32. package/src/compaction/messages.ts +212 -0
  33. package/src/compaction/openai.ts +552 -0
  34. package/src/compaction/prompts/auto-handoff-threshold-focus.md +1 -0
  35. package/src/compaction/prompts/branch-summary-context.md +5 -0
  36. package/src/compaction/prompts/branch-summary-preamble.md +2 -0
  37. package/src/compaction/prompts/branch-summary.md +30 -0
  38. package/src/compaction/prompts/compaction-short-summary.md +9 -0
  39. package/src/compaction/prompts/compaction-summary-context.md +5 -0
  40. package/src/compaction/prompts/compaction-summary.md +38 -0
  41. package/src/compaction/prompts/compaction-turn-prefix.md +17 -0
  42. package/src/compaction/prompts/compaction-update-summary.md +45 -0
  43. package/src/compaction/prompts/file-operations.md +10 -0
  44. package/src/compaction/prompts/handoff-document.md +49 -0
  45. package/src/compaction/prompts/summarization-system.md +3 -0
  46. package/src/compaction/pruning.ts +92 -0
  47. package/src/compaction/utils.ts +185 -0
  48. package/src/compaction.ts +1 -0
  49. package/src/harmony-leak.ts +427 -0
  50. package/src/index.ts +19 -0
  51. package/src/proxy.ts +326 -0
  52. package/src/run-collector.ts +631 -0
  53. package/src/telemetry.ts +2018 -0
  54. package/src/thinking.ts +19 -0
  55. package/src/types.ts +467 -0
package/src/proxy.ts ADDED
@@ -0,0 +1,326 @@
1
+ /**
2
+ * Proxy stream function for apps that route LLM calls through a server.
3
+ * The server manages auth and proxies requests to LLM providers.
4
+ */
5
+ import {
6
+ type AssistantMessage,
7
+ type AssistantMessageEvent,
8
+ type Context,
9
+ EventStream,
10
+ type Model,
11
+ type SimpleStreamOptions,
12
+ type StopReason,
13
+ type ToolCall,
14
+ } from "@gajae-code/ai";
15
+ import { calculateCost } from "@gajae-code/ai/models";
16
+ import { parseStreamingJson } from "@gajae-code/ai/utils/json-parse";
17
+ import { readSseJson } from "@gajae-code/utils";
18
+
19
+ // Create stream class matching ProxyMessageEventStream
20
+ class ProxyMessageEventStream extends EventStream<AssistantMessageEvent, AssistantMessage> {
21
+ constructor() {
22
+ super(
23
+ event => event.type === "done" || event.type === "error",
24
+ event => {
25
+ if (event.type === "done") return event.message;
26
+ if (event.type === "error") return event.error;
27
+ throw new Error("Unexpected event type");
28
+ },
29
+ );
30
+ }
31
+ }
32
+
33
+ /**
34
+ * Proxy event types - server sends these with partial field stripped to reduce bandwidth.
35
+ */
36
+ export type ProxyAssistantMessageEvent =
37
+ | { type: "start" }
38
+ | { type: "text_start"; contentIndex: number }
39
+ | { type: "text_delta"; contentIndex: number; delta: string }
40
+ | { type: "text_end"; contentIndex: number; contentSignature?: string }
41
+ | { type: "thinking_start"; contentIndex: number }
42
+ | { type: "thinking_delta"; contentIndex: number; delta: string }
43
+ | { type: "thinking_end"; contentIndex: number; contentSignature?: string }
44
+ | { type: "toolcall_start"; contentIndex: number; id: string; toolName: string }
45
+ | { type: "toolcall_delta"; contentIndex: number; delta: string }
46
+ | { type: "toolcall_end"; contentIndex: number }
47
+ | {
48
+ type: "done";
49
+ reason: Extract<StopReason, "stop" | "length" | "toolUse">;
50
+ usage: AssistantMessage["usage"];
51
+ }
52
+ | {
53
+ type: "error";
54
+ reason: Extract<StopReason, "aborted" | "error">;
55
+ errorMessage?: string;
56
+ usage: AssistantMessage["usage"];
57
+ };
58
+
59
+ export interface ProxyStreamOptions extends SimpleStreamOptions {
60
+ /** Auth token for the proxy server */
61
+ authToken: string;
62
+ /** Proxy server URL (e.g., "https://genai.example.com") */
63
+ proxyUrl: string;
64
+ }
65
+
66
+ /**
67
+ * Stream function that proxies through a server instead of calling LLM providers directly.
68
+ * The server strips the partial field from delta events to reduce bandwidth.
69
+ * We reconstruct the partial message client-side.
70
+ *
71
+ * Use this as the `streamFn` option when creating an Agent that needs to go through a proxy.
72
+ *
73
+ * @example
74
+ * ```typescript
75
+ * const agent = new Agent({
76
+ * streamFn: (model, context, options) =>
77
+ * streamProxy(model, context, {
78
+ * ...options,
79
+ * authToken: await getAuthToken(),
80
+ * proxyUrl: "https://genai.example.com",
81
+ * }),
82
+ * });
83
+ * ```
84
+ */
85
+ export function streamProxy(model: Model, context: Context, options: ProxyStreamOptions): ProxyMessageEventStream {
86
+ const stream = new ProxyMessageEventStream();
87
+
88
+ (async () => {
89
+ // Initialize the partial message that we'll build up from events
90
+ const partial: AssistantMessage = {
91
+ role: "assistant",
92
+ stopReason: "stop",
93
+ content: [],
94
+ api: model.api,
95
+ provider: model.provider,
96
+ model: model.id,
97
+ usage: {
98
+ input: 0,
99
+ output: 0,
100
+ cacheRead: 0,
101
+ cacheWrite: 0,
102
+ totalTokens: 0,
103
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
104
+ },
105
+ timestamp: Date.now(),
106
+ };
107
+
108
+ let response: Response | null = null;
109
+ const abortHandler = () => {
110
+ const body = response?.body;
111
+ if (body) {
112
+ body.cancel("Request aborted by user").catch(() => {});
113
+ }
114
+ };
115
+ if (options.signal) {
116
+ options.signal.addEventListener("abort", abortHandler, { once: true });
117
+ }
118
+
119
+ try {
120
+ response = await fetch(`${options.proxyUrl}/api/stream`, {
121
+ method: "POST",
122
+ headers: {
123
+ Authorization: `Bearer ${options.authToken}`,
124
+ "Content-Type": "application/json",
125
+ },
126
+ body: JSON.stringify({
127
+ model,
128
+ context,
129
+ options: {
130
+ temperature: options.temperature,
131
+ topP: options.topP,
132
+ topK: options.topK,
133
+ minP: options.minP,
134
+ presencePenalty: options.presencePenalty,
135
+ repetitionPenalty: options.repetitionPenalty,
136
+ maxTokens: options.maxTokens,
137
+ reasoning: options.reasoning,
138
+ },
139
+ }),
140
+ signal: options.signal,
141
+ });
142
+
143
+ if (!response.ok) {
144
+ let errorMessage = `Proxy error: ${response.status} ${response.statusText}`;
145
+ try {
146
+ const errorData = (await response.json()) as { error?: string };
147
+ if (errorData.error) {
148
+ errorMessage = `Proxy error: ${errorData.error}`;
149
+ }
150
+ } catch {
151
+ // Couldn't parse error response
152
+ }
153
+ throw new Error(errorMessage);
154
+ }
155
+
156
+ let sawTerminalEvent = false;
157
+ for await (const event of readSseJson<ProxyAssistantMessageEvent>(
158
+ response.body as ReadableStream<Uint8Array>,
159
+ options.signal,
160
+ )) {
161
+ const parsedEvent = processProxyEvent(model, event, partial);
162
+ if (parsedEvent) {
163
+ if (parsedEvent.type === "done" || parsedEvent.type === "error") {
164
+ sawTerminalEvent = true;
165
+ }
166
+ stream.push(parsedEvent);
167
+ }
168
+ }
169
+
170
+ if (options.signal?.aborted && !sawTerminalEvent) {
171
+ const reason = options.signal.reason;
172
+ throw reason instanceof Error ? reason : new Error(String(reason ?? "Request aborted"));
173
+ }
174
+
175
+ stream.end();
176
+ } catch (error) {
177
+ const errorMessage = error instanceof Error ? error.message : String(error);
178
+ const reason = options.signal?.aborted ? "aborted" : "error";
179
+ partial.stopReason = reason;
180
+ partial.errorMessage = errorMessage;
181
+ stream.push({
182
+ type: "error",
183
+ reason,
184
+ error: partial,
185
+ });
186
+ stream.end();
187
+ } finally {
188
+ if (options.signal) {
189
+ options.signal.removeEventListener("abort", abortHandler);
190
+ }
191
+ }
192
+ })();
193
+
194
+ return stream;
195
+ }
196
+
197
+ /**
198
+ * Process a proxy event and update the partial message.
199
+ */
200
+ function processProxyEvent(
201
+ model: Model,
202
+ proxyEvent: ProxyAssistantMessageEvent,
203
+ partial: AssistantMessage,
204
+ ): AssistantMessageEvent | undefined {
205
+ switch (proxyEvent.type) {
206
+ case "start":
207
+ return { type: "start", partial };
208
+
209
+ case "text_start":
210
+ partial.content[proxyEvent.contentIndex] = { type: "text", text: "" };
211
+ return { type: "text_start", contentIndex: proxyEvent.contentIndex, partial };
212
+
213
+ case "text_delta": {
214
+ const content = partial.content[proxyEvent.contentIndex];
215
+ if (content?.type === "text") {
216
+ content.text += proxyEvent.delta;
217
+ return {
218
+ type: "text_delta",
219
+ contentIndex: proxyEvent.contentIndex,
220
+ delta: proxyEvent.delta,
221
+ partial,
222
+ };
223
+ }
224
+ throw new Error("Received text_delta for non-text content");
225
+ }
226
+
227
+ case "text_end": {
228
+ const content = partial.content[proxyEvent.contentIndex];
229
+ if (content?.type === "text") {
230
+ content.textSignature = proxyEvent.contentSignature;
231
+ return {
232
+ type: "text_end",
233
+ contentIndex: proxyEvent.contentIndex,
234
+ content: content.text,
235
+ partial,
236
+ };
237
+ }
238
+ throw new Error("Received text_end for non-text content");
239
+ }
240
+
241
+ case "thinking_start":
242
+ partial.content[proxyEvent.contentIndex] = { type: "thinking", thinking: "" };
243
+ return { type: "thinking_start", contentIndex: proxyEvent.contentIndex, partial };
244
+
245
+ case "thinking_delta": {
246
+ const content = partial.content[proxyEvent.contentIndex];
247
+ if (content?.type === "thinking") {
248
+ content.thinking += proxyEvent.delta;
249
+ return {
250
+ type: "thinking_delta",
251
+ contentIndex: proxyEvent.contentIndex,
252
+ delta: proxyEvent.delta,
253
+ partial,
254
+ };
255
+ }
256
+ throw new Error("Received thinking_delta for non-thinking content");
257
+ }
258
+
259
+ case "thinking_end": {
260
+ const content = partial.content[proxyEvent.contentIndex];
261
+ if (content?.type === "thinking") {
262
+ content.thinkingSignature = proxyEvent.contentSignature;
263
+ return {
264
+ type: "thinking_end",
265
+ contentIndex: proxyEvent.contentIndex,
266
+ content: content.thinking,
267
+ partial,
268
+ };
269
+ }
270
+ throw new Error("Received thinking_end for non-thinking content");
271
+ }
272
+
273
+ case "toolcall_start":
274
+ partial.content[proxyEvent.contentIndex] = {
275
+ type: "toolCall",
276
+ id: proxyEvent.id,
277
+ name: proxyEvent.toolName,
278
+ arguments: {},
279
+ partialJson: "",
280
+ } satisfies ToolCall & { partialJson: string } as ToolCall;
281
+ return { type: "toolcall_start", contentIndex: proxyEvent.contentIndex, partial };
282
+
283
+ case "toolcall_delta": {
284
+ const content = partial.content[proxyEvent.contentIndex];
285
+ if (content?.type === "toolCall") {
286
+ (content as any).partialJson += proxyEvent.delta;
287
+ content.arguments = parseStreamingJson((content as any).partialJson) || {};
288
+ partial.content[proxyEvent.contentIndex] = { ...content }; // Trigger reactivity
289
+ return {
290
+ type: "toolcall_delta",
291
+ contentIndex: proxyEvent.contentIndex,
292
+ delta: proxyEvent.delta,
293
+ partial,
294
+ };
295
+ }
296
+ throw new Error("Received toolcall_delta for non-toolCall content");
297
+ }
298
+
299
+ case "toolcall_end": {
300
+ const content = partial.content[proxyEvent.contentIndex];
301
+ if (content?.type === "toolCall") {
302
+ delete (content as any).partialJson;
303
+ return {
304
+ type: "toolcall_end",
305
+ contentIndex: proxyEvent.contentIndex,
306
+ toolCall: content,
307
+ partial,
308
+ };
309
+ }
310
+ return undefined;
311
+ }
312
+
313
+ case "done":
314
+ partial.stopReason = proxyEvent.reason;
315
+ partial.usage = proxyEvent.usage;
316
+ calculateCost(model, partial.usage);
317
+ return { type: "done", reason: proxyEvent.reason, message: partial };
318
+
319
+ case "error":
320
+ partial.stopReason = proxyEvent.reason;
321
+ partial.errorMessage = proxyEvent.errorMessage;
322
+ partial.usage = proxyEvent.usage;
323
+ calculateCost(model, partial.usage);
324
+ return { type: "error", reason: proxyEvent.reason, error: partial };
325
+ }
326
+ }