jeopi-agent-core 16.2.13

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 (66) hide show
  1. package/CHANGELOG.md +1016 -0
  2. package/README.md +473 -0
  3. package/dist/types/agent-loop.d.ts +66 -0
  4. package/dist/types/agent.d.ts +427 -0
  5. package/dist/types/append-only-context.d.ts +133 -0
  6. package/dist/types/compaction/branch-summarization.d.ts +101 -0
  7. package/dist/types/compaction/compaction-v2-streaming.d.ts +82 -0
  8. package/dist/types/compaction/compaction.d.ts +283 -0
  9. package/dist/types/compaction/entries.d.ts +110 -0
  10. package/dist/types/compaction/errors.d.ts +26 -0
  11. package/dist/types/compaction/index.d.ts +12 -0
  12. package/dist/types/compaction/messages.d.ts +77 -0
  13. package/dist/types/compaction/openai.d.ts +77 -0
  14. package/dist/types/compaction/pruning.d.ts +105 -0
  15. package/dist/types/compaction/shake.d.ts +92 -0
  16. package/dist/types/compaction/tool-protection.d.ts +17 -0
  17. package/dist/types/compaction/utils.d.ts +58 -0
  18. package/dist/types/compaction.d.ts +1 -0
  19. package/dist/types/index.d.ts +12 -0
  20. package/dist/types/proxy.d.ts +85 -0
  21. package/dist/types/replay-policy.d.ts +5 -0
  22. package/dist/types/run-collector.d.ts +196 -0
  23. package/dist/types/telemetry.d.ts +590 -0
  24. package/dist/types/thinking.d.ts +17 -0
  25. package/dist/types/tokenizer.d.ts +1 -0
  26. package/dist/types/types.d.ts +640 -0
  27. package/dist/types/utils/yield.d.ts +71 -0
  28. package/package.json +78 -0
  29. package/src/agent-loop.ts +2188 -0
  30. package/src/agent.ts +1457 -0
  31. package/src/append-only-context.ts +348 -0
  32. package/src/compaction/branch-summarization.ts +370 -0
  33. package/src/compaction/compaction-v2-streaming.ts +719 -0
  34. package/src/compaction/compaction.ts +1553 -0
  35. package/src/compaction/entries.ts +142 -0
  36. package/src/compaction/errors.ts +31 -0
  37. package/src/compaction/index.ts +13 -0
  38. package/src/compaction/messages.ts +237 -0
  39. package/src/compaction/openai.ts +581 -0
  40. package/src/compaction/prompts/auto-handoff-threshold-focus.md +1 -0
  41. package/src/compaction/prompts/branch-summary-context.md +5 -0
  42. package/src/compaction/prompts/branch-summary-preamble.md +2 -0
  43. package/src/compaction/prompts/branch-summary.md +30 -0
  44. package/src/compaction/prompts/compaction-short-summary.md +9 -0
  45. package/src/compaction/prompts/compaction-summary-context.md +5 -0
  46. package/src/compaction/prompts/compaction-summary.md +38 -0
  47. package/src/compaction/prompts/compaction-turn-prefix.md +17 -0
  48. package/src/compaction/prompts/compaction-update-summary.md +45 -0
  49. package/src/compaction/prompts/file-operations.md +5 -0
  50. package/src/compaction/prompts/handoff-document.md +49 -0
  51. package/src/compaction/prompts/snapcompact-archive-context.md +3 -0
  52. package/src/compaction/prompts/summarization-system.md +3 -0
  53. package/src/compaction/pruning.ts +424 -0
  54. package/src/compaction/shake.ts +429 -0
  55. package/src/compaction/tool-protection.ts +55 -0
  56. package/src/compaction/utils.ts +323 -0
  57. package/src/compaction.ts +1 -0
  58. package/src/index.ts +24 -0
  59. package/src/proxy.ts +376 -0
  60. package/src/replay-policy.ts +13 -0
  61. package/src/run-collector.ts +631 -0
  62. package/src/telemetry.ts +2034 -0
  63. package/src/thinking.ts +19 -0
  64. package/src/tokenizer.ts +17 -0
  65. package/src/types.ts +718 -0
  66. package/src/utils/yield.ts +183 -0
package/src/proxy.ts ADDED
@@ -0,0 +1,376 @@
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 FetchImpl,
11
+ type Model,
12
+ type SimpleStreamOptions,
13
+ type StopReason,
14
+ type ToolCall,
15
+ } from "jeopi-ai";
16
+ import {
17
+ clearStreamingPartialJson,
18
+ kStreamingPartialJson,
19
+ type StreamingPartialJsonCarrier,
20
+ setStreamingPartialJson,
21
+ } from "jeopi-ai/utils/block-symbols";
22
+ import { calculateCost } from "jeopi-catalog/models";
23
+ import { parseStreamingJson, readSseJson } from "jeopi-utils";
24
+
25
+ // Event stream adapter for proxy SSE events
26
+ export class ProxyMessageEventStream extends EventStream<AssistantMessageEvent, AssistantMessage> {
27
+ constructor() {
28
+ super(
29
+ event => event.type === "done" || event.type === "error",
30
+ event => {
31
+ if (event.type === "done") return event.message;
32
+ if (event.type === "error") return event.error;
33
+ throw new Error("Unexpected event type");
34
+ },
35
+ );
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Proxy event types - server sends these with partial field stripped to reduce bandwidth.
41
+ */
42
+ export type ProxyAssistantMessageEvent =
43
+ | { type: "start" }
44
+ | { type: "text_start"; contentIndex: number }
45
+ | { type: "text_delta"; contentIndex: number; delta: string }
46
+ | { type: "text_end"; contentIndex: number; contentSignature?: string }
47
+ | { type: "thinking_start"; contentIndex: number }
48
+ | { type: "thinking_delta"; contentIndex: number; delta: string }
49
+ | { type: "thinking_end"; contentIndex: number; contentSignature?: string }
50
+ | { type: "toolcall_start"; contentIndex: number; id: string; toolName: string }
51
+ | { type: "toolcall_delta"; contentIndex: number; delta: string }
52
+ | { type: "toolcall_end"; contentIndex: number }
53
+ | {
54
+ type: "done";
55
+ reason: Extract<StopReason, "stop" | "length" | "toolUse">;
56
+ usage: AssistantMessage["usage"];
57
+ }
58
+ | {
59
+ type: "error";
60
+ reason: Extract<StopReason, "aborted" | "error">;
61
+ errorMessage?: string;
62
+ usage: AssistantMessage["usage"];
63
+ };
64
+
65
+ export interface ProxyStreamOptions extends SimpleStreamOptions {
66
+ /** Auth token for the proxy server */
67
+ authToken: string;
68
+ /** Proxy server URL (e.g., "https://genai.example.com") */
69
+ proxyUrl: string;
70
+ /** Optional fetch implementation; defaults to global fetch. */
71
+ fetch?: FetchImpl;
72
+ }
73
+
74
+ /**
75
+ * Stream function that proxies through a server instead of calling LLM providers directly.
76
+ * The server strips the partial field from delta events to reduce bandwidth.
77
+ * We reconstruct the partial message client-side.
78
+ *
79
+ * Use this as the `streamFn` option when creating an Agent that needs to go through a proxy.
80
+ *
81
+ * @example
82
+ * ```typescript
83
+ * const agent = new Agent({
84
+ * streamFn: (model, context, options) =>
85
+ * streamProxy(model, context, {
86
+ * ...options,
87
+ * authToken: await getAuthToken(),
88
+ * proxyUrl: "https://genai.example.com",
89
+ * }),
90
+ * });
91
+ * ```
92
+ */
93
+ export function streamProxy(model: Model, context: Context, options: ProxyStreamOptions): ProxyMessageEventStream {
94
+ const stream = new ProxyMessageEventStream();
95
+
96
+ (async () => {
97
+ // Initialize the partial message that we'll build up from events
98
+ const partial: AssistantMessage = {
99
+ role: "assistant",
100
+ stopReason: "stop",
101
+ content: [],
102
+ api: model.api,
103
+ provider: model.provider,
104
+ model: model.id,
105
+ usage: {
106
+ input: 0,
107
+ output: 0,
108
+ cacheRead: 0,
109
+ cacheWrite: 0,
110
+ totalTokens: 0,
111
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
112
+ },
113
+ timestamp: Date.now(),
114
+ };
115
+
116
+ let response: Response | null = null;
117
+ const abortHandler = () => {
118
+ const body = response?.body;
119
+ if (body) {
120
+ body.cancel("Request aborted by user").catch(() => {});
121
+ }
122
+ };
123
+ if (options.signal) {
124
+ options.signal.addEventListener("abort", abortHandler, { once: true });
125
+ }
126
+
127
+ try {
128
+ response = await (options.fetch ?? fetch)(`${options.proxyUrl}/api/stream`, {
129
+ method: "POST",
130
+ headers: {
131
+ Authorization: `Bearer ${options.authToken}`,
132
+ "Content-Type": "application/json",
133
+ },
134
+ body: JSON.stringify({
135
+ model,
136
+ context,
137
+ options: {
138
+ temperature: options.temperature,
139
+ topP: options.topP,
140
+ topK: options.topK,
141
+ minP: options.minP,
142
+ presencePenalty: options.presencePenalty,
143
+ repetitionPenalty: options.repetitionPenalty,
144
+ maxTokens: options.maxTokens,
145
+ reasoning: options.reasoning,
146
+ },
147
+ }),
148
+ signal: options.signal,
149
+ });
150
+
151
+ if (!response.ok) {
152
+ let errorMessage = `Proxy error: ${response.status} ${response.statusText}`;
153
+ try {
154
+ const errorData = (await response.json()) as { error?: string };
155
+ if (errorData.error) {
156
+ errorMessage = `Proxy error: ${errorData.error}`;
157
+ }
158
+ } catch {
159
+ // Couldn't parse error response
160
+ }
161
+ throw new Error(errorMessage);
162
+ }
163
+
164
+ let sawTerminalEvent = false;
165
+ const partialJsonByIndex = new Map<number, string>();
166
+ for await (const event of readSseJson<ProxyAssistantMessageEvent>(
167
+ response.body as ReadableStream<Uint8Array>,
168
+ options.signal,
169
+ )) {
170
+ const parsedEvent = processProxyEvent(model, event, partial, partialJsonByIndex);
171
+ if (parsedEvent) {
172
+ if (parsedEvent.type === "done" || parsedEvent.type === "error") {
173
+ sawTerminalEvent = true;
174
+ }
175
+ stream.push(parsedEvent);
176
+ }
177
+ }
178
+
179
+ if (!sawTerminalEvent) {
180
+ if (options.signal?.aborted) {
181
+ const reason = options.signal.reason;
182
+ throw reason instanceof Error ? reason : new Error(String(reason ?? "Request aborted"));
183
+ }
184
+ throw new Error("Proxy stream ended without a terminal event (done or error)");
185
+ }
186
+
187
+ stream.end();
188
+ } catch (error) {
189
+ const errorMessage = error instanceof Error ? error.message : String(error);
190
+ const reason = options.signal?.aborted ? "aborted" : "error";
191
+ partial.stopReason = reason;
192
+ partial.errorMessage = errorMessage;
193
+ scrubPartialJson(partial);
194
+ stream.push({
195
+ type: "error",
196
+ reason,
197
+ error: partial,
198
+ });
199
+ stream.end();
200
+ } finally {
201
+ if (options.signal) {
202
+ options.signal.removeEventListener("abort", abortHandler);
203
+ }
204
+ }
205
+ })();
206
+
207
+ return stream;
208
+ }
209
+
210
+ /**
211
+ * Clear the `partialJson` streaming symbol from any tool-call content blocks
212
+ * that still carry it (e.g. when the stream ended without a `toolcall_end`), so
213
+ * the finalized `AssistantMessage` no longer reads as still-streaming.
214
+ */
215
+ function scrubPartialJson(partial: AssistantMessage): void {
216
+ for (const block of partial.content) {
217
+ if (block?.type === "toolCall") clearStreamingPartialJson(block);
218
+ }
219
+ }
220
+
221
+ /**
222
+ * Process a proxy event and update the partial message.
223
+ *
224
+ * Streaming `partialJson` for in-progress tool calls is accumulated in a
225
+ * side-channel map keyed by `contentIndex` and also written onto the content
226
+ * object as a symbol-keyed field so downstream renderers can read it
227
+ * during streaming. The field is cleared at `toolcall_end` and scrubbed from any
228
+ * remaining blocks at `done`/`error` so the finalized `AssistantMessage` never
229
+ * reads as still-streaming.
230
+ */
231
+ function processProxyEvent(
232
+ model: Model,
233
+ proxyEvent: ProxyAssistantMessageEvent,
234
+ partial: AssistantMessage,
235
+ partialJsonByIndex: Map<number, string>,
236
+ ): AssistantMessageEvent | undefined {
237
+ switch (proxyEvent.type) {
238
+ case "start":
239
+ partial.content.length = 0;
240
+ partial.usage = {
241
+ input: 0,
242
+ output: 0,
243
+ cacheRead: 0,
244
+ cacheWrite: 0,
245
+ totalTokens: 0,
246
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
247
+ };
248
+ partial.errorMessage = undefined;
249
+ partial.errorId = undefined;
250
+ partial.duration = undefined;
251
+ (partial as { stopReason?: string }).stopReason = undefined;
252
+ return { type: "start", partial };
253
+
254
+ case "text_start":
255
+ partial.content[proxyEvent.contentIndex] = { type: "text", text: "" };
256
+ return { type: "text_start", contentIndex: proxyEvent.contentIndex, partial };
257
+
258
+ case "text_delta": {
259
+ const content = partial.content[proxyEvent.contentIndex];
260
+ if (content?.type === "text") {
261
+ content.text += proxyEvent.delta;
262
+ return {
263
+ type: "text_delta",
264
+ contentIndex: proxyEvent.contentIndex,
265
+ delta: proxyEvent.delta,
266
+ partial,
267
+ };
268
+ }
269
+ throw new Error("Received text_delta for non-text content");
270
+ }
271
+
272
+ case "text_end": {
273
+ const content = partial.content[proxyEvent.contentIndex];
274
+ if (content?.type === "text") {
275
+ content.textSignature = proxyEvent.contentSignature;
276
+ return {
277
+ type: "text_end",
278
+ contentIndex: proxyEvent.contentIndex,
279
+ content: content.text,
280
+ partial,
281
+ };
282
+ }
283
+ throw new Error("Received text_end for non-text content");
284
+ }
285
+
286
+ case "thinking_start":
287
+ partial.content[proxyEvent.contentIndex] = { type: "thinking", thinking: "" };
288
+ return { type: "thinking_start", contentIndex: proxyEvent.contentIndex, partial };
289
+
290
+ case "thinking_delta": {
291
+ const content = partial.content[proxyEvent.contentIndex];
292
+ if (content?.type === "thinking") {
293
+ content.thinking += proxyEvent.delta;
294
+ return {
295
+ type: "thinking_delta",
296
+ contentIndex: proxyEvent.contentIndex,
297
+ delta: proxyEvent.delta,
298
+ partial,
299
+ };
300
+ }
301
+ throw new Error("Received thinking_delta for non-thinking content");
302
+ }
303
+
304
+ case "thinking_end": {
305
+ const content = partial.content[proxyEvent.contentIndex];
306
+ if (content?.type === "thinking") {
307
+ content.thinkingSignature = proxyEvent.contentSignature;
308
+ return {
309
+ type: "thinking_end",
310
+ contentIndex: proxyEvent.contentIndex,
311
+ content: content.thinking,
312
+ partial,
313
+ };
314
+ }
315
+ throw new Error("Received thinking_end for non-thinking content");
316
+ }
317
+
318
+ case "toolcall_start":
319
+ partial.content[proxyEvent.contentIndex] = {
320
+ type: "toolCall",
321
+ id: proxyEvent.id,
322
+ name: proxyEvent.toolName,
323
+ arguments: {},
324
+ [kStreamingPartialJson]: "",
325
+ } as ToolCall & StreamingPartialJsonCarrier;
326
+ partialJsonByIndex.set(proxyEvent.contentIndex, "");
327
+ return { type: "toolcall_start", contentIndex: proxyEvent.contentIndex, partial };
328
+ case "toolcall_delta": {
329
+ const content = partial.content[proxyEvent.contentIndex];
330
+ if (content?.type === "toolCall") {
331
+ const acc = (partialJsonByIndex.get(proxyEvent.contentIndex) ?? "") + proxyEvent.delta;
332
+ partialJsonByIndex.set(proxyEvent.contentIndex, acc);
333
+ content.arguments = parseStreamingJson(acc) || {};
334
+ setStreamingPartialJson(content, acc);
335
+ partial.content[proxyEvent.contentIndex] = { ...content }; // Trigger reactivity
336
+ return {
337
+ type: "toolcall_delta",
338
+ contentIndex: proxyEvent.contentIndex,
339
+ delta: proxyEvent.delta,
340
+ partial,
341
+ };
342
+ }
343
+ throw new Error("Received toolcall_delta for non-toolCall content");
344
+ }
345
+
346
+ case "toolcall_end": {
347
+ const content = partial.content[proxyEvent.contentIndex];
348
+ if (content?.type === "toolCall") {
349
+ partialJsonByIndex.delete(proxyEvent.contentIndex);
350
+ clearStreamingPartialJson(content);
351
+ return {
352
+ type: "toolcall_end",
353
+ contentIndex: proxyEvent.contentIndex,
354
+ toolCall: content,
355
+ partial,
356
+ };
357
+ }
358
+ return undefined;
359
+ }
360
+
361
+ case "done":
362
+ partial.stopReason = proxyEvent.reason;
363
+ partial.usage = proxyEvent.usage;
364
+ calculateCost(model, partial.usage);
365
+ scrubPartialJson(partial);
366
+ return { type: "done", reason: proxyEvent.reason, message: partial };
367
+
368
+ case "error":
369
+ partial.stopReason = proxyEvent.reason;
370
+ partial.errorMessage = proxyEvent.errorMessage;
371
+ partial.usage = proxyEvent.usage;
372
+ calculateCost(model, partial.usage);
373
+ scrubPartialJson(partial);
374
+ return { type: "error", reason: proxyEvent.reason, error: partial };
375
+ }
376
+ }
@@ -0,0 +1,13 @@
1
+ import type { AssistantMessage, Message } from "jeopi-ai";
2
+
3
+ /** Detects API-level provider refusals that are terminal errors, not dialogue to replay. */
4
+ export function isProviderRefusalMessage(message: AssistantMessage): boolean {
5
+ if (message.stopReason !== "error") return false;
6
+ const stopType = message.stopDetails?.type;
7
+ return stopType === "refusal" || stopType === "sensitive";
8
+ }
9
+
10
+ /** Removes API-level provider refusals from live provider replay while preserving other messages. */
11
+ export function filterProviderReplayMessages(messages: readonly Message[]): Message[] {
12
+ return messages.filter(message => message.role !== "assistant" || !isProviderRefusalMessage(message));
13
+ }