@eggjs/agent-runtime 3.73.0-beta.0 → 4.0.2-beta.4

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 (44) hide show
  1. package/LICENSE +21 -0
  2. package/dist/AgentRuntime.d.ts +46 -0
  3. package/dist/AgentRuntime.js +298 -0
  4. package/dist/AgentStoreUtils.d.ts +7 -0
  5. package/dist/AgentStoreUtils.js +18 -0
  6. package/dist/HttpSSEWriter.d.ts +20 -0
  7. package/dist/HttpSSEWriter.js +49 -0
  8. package/dist/MessageConverter.d.ts +36 -0
  9. package/dist/MessageConverter.js +123 -0
  10. package/dist/OSSAgentStore.d.ts +66 -0
  11. package/dist/OSSAgentStore.js +133 -0
  12. package/dist/OSSObjectStorageClient.d.ts +51 -0
  13. package/dist/OSSObjectStorageClient.js +78 -0
  14. package/dist/RunBuilder.d.ts +47 -0
  15. package/dist/RunBuilder.js +129 -0
  16. package/dist/SSEWriter.d.ts +17 -0
  17. package/dist/SSEWriter.js +1 -0
  18. package/dist/index.d.ts +10 -10
  19. package/dist/index.js +11 -31
  20. package/package.json +35 -25
  21. package/dist/src/AgentRuntime.d.ts +0 -42
  22. package/dist/src/AgentRuntime.js +0 -376
  23. package/dist/src/AgentStore.d.ts +0 -42
  24. package/dist/src/AgentStore.js +0 -3
  25. package/dist/src/AgentStoreUtils.d.ts +0 -4
  26. package/dist/src/AgentStoreUtils.js +0 -23
  27. package/dist/src/FileAgentStore.d.ts +0 -21
  28. package/dist/src/FileAgentStore.js +0 -104
  29. package/dist/src/HttpSSEWriter.d.ts +0 -16
  30. package/dist/src/HttpSSEWriter.js +0 -51
  31. package/dist/src/MessageConverter.d.ts +0 -32
  32. package/dist/src/MessageConverter.js +0 -108
  33. package/dist/src/OSSAgentStore.d.ts +0 -62
  34. package/dist/src/OSSAgentStore.js +0 -165
  35. package/dist/src/OSSObjectStorageClient.d.ts +0 -46
  36. package/dist/src/OSSObjectStorageClient.js +0 -90
  37. package/dist/src/RunBuilder.d.ts +0 -43
  38. package/dist/src/RunBuilder.js +0 -127
  39. package/dist/src/SSEWriter.d.ts +0 -14
  40. package/dist/src/SSEWriter.js +0 -3
  41. package/dist/src/agentDefaults.d.ts +0 -1
  42. package/dist/src/agentDefaults.js +0 -447
  43. package/dist/src/enhanceAgentController.d.ts +0 -2
  44. package/dist/src/enhanceAgentController.js +0 -90
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2017-present Alibaba Group Holding Limited and other contributors.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,46 @@
1
+ import { SSEWriter } from "./SSEWriter.js";
2
+ import { AgentStore, AgentStreamMessage, CreateRunInput, RunObject, ThreadObject, ThreadObjectWithMessages } from "@eggjs/tegg-types/agent-runtime";
3
+ import { EggLogger } from "egg-logger";
4
+
5
+ //#region src/AgentRuntime.d.ts
6
+ declare const AGENT_RUNTIME: unique symbol;
7
+ /**
8
+ * The executor interface — only requires execRun so the runtime can delegate
9
+ * execution back through the controller's prototype chain (AOP/mock friendly).
10
+ */
11
+ interface AgentExecutor {
12
+ execRun(input: CreateRunInput, signal?: AbortSignal): AsyncGenerator<AgentStreamMessage>;
13
+ }
14
+ interface AgentRuntimeOptions {
15
+ executor: AgentExecutor;
16
+ store: AgentStore;
17
+ logger: EggLogger;
18
+ }
19
+ declare class AgentRuntime {
20
+ private static readonly TERMINAL_RUN_STATUSES;
21
+ private store;
22
+ private runningTasks;
23
+ private executor;
24
+ private logger;
25
+ constructor(options: AgentRuntimeOptions);
26
+ createThread(): Promise<ThreadObject>;
27
+ getThread(threadId: string): Promise<ThreadObjectWithMessages>;
28
+ private ensureThread;
29
+ syncRun(input: CreateRunInput, signal?: AbortSignal): Promise<RunObject>;
30
+ asyncRun(input: CreateRunInput): Promise<RunObject>;
31
+ streamRun(input: CreateRunInput, writer: SSEWriter): Promise<void>;
32
+ /**
33
+ * Consume the execRun async generator, emitting SSE message.delta events
34
+ * for each chunk and accumulating content blocks and token usage.
35
+ */
36
+ private consumeStreamMessages;
37
+ getRun(runId: string): Promise<RunObject>;
38
+ cancelRun(runId: string): Promise<RunObject>;
39
+ /** Wait for all in-flight background tasks to complete naturally (without aborting). */
40
+ waitForPendingTasks(): Promise<void>;
41
+ destroy(): Promise<void>;
42
+ /** Factory method — avoids the spread-arg type issue with dynamic delegation. */
43
+ static create(options: AgentRuntimeOptions): AgentRuntime;
44
+ }
45
+ //#endregion
46
+ export { AGENT_RUNTIME, AgentExecutor, AgentRuntime, AgentRuntimeOptions };
@@ -0,0 +1,298 @@
1
+ import { newMsgId } from "./AgentStoreUtils.js";
2
+ import { MessageConverter } from "./MessageConverter.js";
3
+ import { RunBuilder } from "./RunBuilder.js";
4
+ import { AgentConflictError, AgentObjectType, AgentSSEEvent, RunStatus } from "@eggjs/tegg-types/agent-runtime";
5
+
6
+ //#region src/AgentRuntime.ts
7
+ const AGENT_RUNTIME = Symbol("agentRuntime");
8
+ var AgentRuntime = class AgentRuntime {
9
+ static TERMINAL_RUN_STATUSES = new Set([
10
+ RunStatus.Completed,
11
+ RunStatus.Failed,
12
+ RunStatus.Cancelled,
13
+ RunStatus.Expired
14
+ ]);
15
+ store;
16
+ runningTasks;
17
+ executor;
18
+ logger;
19
+ constructor(options) {
20
+ this.executor = options.executor;
21
+ this.store = options.store;
22
+ if (!options.logger) throw new Error("AgentRuntimeOptions.logger is required");
23
+ this.logger = options.logger;
24
+ this.runningTasks = /* @__PURE__ */ new Map();
25
+ }
26
+ async createThread() {
27
+ const thread = await this.store.createThread();
28
+ return {
29
+ id: thread.id,
30
+ object: AgentObjectType.Thread,
31
+ createdAt: thread.createdAt,
32
+ metadata: thread.metadata ?? {}
33
+ };
34
+ }
35
+ async getThread(threadId) {
36
+ const thread = await this.store.getThread(threadId);
37
+ return {
38
+ id: thread.id,
39
+ object: AgentObjectType.Thread,
40
+ createdAt: thread.createdAt,
41
+ metadata: thread.metadata ?? {},
42
+ messages: thread.messages
43
+ };
44
+ }
45
+ async ensureThread(input) {
46
+ if (input.threadId) return {
47
+ threadId: input.threadId,
48
+ input
49
+ };
50
+ const thread = await this.store.createThread();
51
+ return {
52
+ threadId: thread.id,
53
+ input: {
54
+ ...input,
55
+ threadId: thread.id
56
+ }
57
+ };
58
+ }
59
+ async syncRun(input, signal) {
60
+ const { threadId, input: resolvedInput } = await this.ensureThread(input);
61
+ input = resolvedInput;
62
+ const run = await this.store.createRun(input.input.messages, threadId, input.config, input.metadata);
63
+ const rb = RunBuilder.create(run, threadId);
64
+ const abortController = new AbortController();
65
+ if (signal) if (signal.aborted) abortController.abort();
66
+ else signal.addEventListener("abort", () => abortController.abort(), { once: true });
67
+ let resolveTask;
68
+ const taskPromise = new Promise((r) => {
69
+ resolveTask = r;
70
+ });
71
+ this.runningTasks.set(run.id, {
72
+ promise: taskPromise,
73
+ abortController
74
+ });
75
+ try {
76
+ await this.store.updateRun(run.id, rb.start());
77
+ const streamMessages = [];
78
+ for await (const msg of this.executor.execRun(input, abortController.signal)) {
79
+ if (abortController.signal.aborted) {
80
+ const latest = await this.store.getRun(run.id);
81
+ return RunBuilder.fromRecord(latest).snapshot();
82
+ }
83
+ streamMessages.push(msg);
84
+ }
85
+ const { output, usage } = MessageConverter.extractFromStreamMessages(streamMessages, run.id);
86
+ await this.store.appendMessages(threadId, [...MessageConverter.toInputMessageObjects(input.input.messages, threadId), ...output]);
87
+ await this.store.updateRun(run.id, rb.complete(output, usage));
88
+ return rb.snapshot();
89
+ } catch (err) {
90
+ if (abortController.signal.aborted) {
91
+ const latest = await this.store.getRun(run.id);
92
+ return RunBuilder.fromRecord(latest).snapshot();
93
+ }
94
+ try {
95
+ await this.store.updateRun(run.id, rb.fail(err));
96
+ } catch (storeErr) {
97
+ this.logger.error("[AgentRuntime] failed to update run status after syncRun error:", storeErr);
98
+ }
99
+ throw err;
100
+ } finally {
101
+ resolveTask();
102
+ this.runningTasks.delete(run.id);
103
+ }
104
+ }
105
+ async asyncRun(input) {
106
+ const { threadId, input: resolvedInput } = await this.ensureThread(input);
107
+ input = resolvedInput;
108
+ const run = await this.store.createRun(input.input.messages, threadId, input.config, input.metadata);
109
+ const rb = RunBuilder.create(run, threadId);
110
+ const abortController = new AbortController();
111
+ const queuedSnapshot = rb.snapshot();
112
+ let resolveTask;
113
+ const taskPromise = new Promise((r) => {
114
+ resolveTask = r;
115
+ });
116
+ this.runningTasks.set(run.id, {
117
+ promise: taskPromise,
118
+ abortController
119
+ });
120
+ (async () => {
121
+ try {
122
+ await this.store.updateRun(run.id, rb.start());
123
+ const streamMessages = [];
124
+ for await (const msg of this.executor.execRun(input, abortController.signal)) {
125
+ if (abortController.signal.aborted) return;
126
+ streamMessages.push(msg);
127
+ }
128
+ const currentRun = await this.store.getRun(run.id);
129
+ if (currentRun.status === RunStatus.Cancelling || currentRun.status === RunStatus.Cancelled) return;
130
+ const { output, usage } = MessageConverter.extractFromStreamMessages(streamMessages, run.id);
131
+ await this.store.appendMessages(threadId, [...MessageConverter.toInputMessageObjects(input.input.messages, threadId), ...output]);
132
+ await this.store.updateRun(run.id, rb.complete(output, usage));
133
+ } catch (err) {
134
+ if (!abortController.signal.aborted) try {
135
+ const currentRun = await this.store.getRun(run.id);
136
+ if (currentRun.status !== RunStatus.Cancelling && currentRun.status !== RunStatus.Cancelled) await this.store.updateRun(run.id, rb.fail(err));
137
+ } catch (storeErr) {
138
+ this.logger.error("[AgentRuntime] failed to update run status after error:", storeErr);
139
+ }
140
+ else this.logger.error("[AgentRuntime] execRun error during abort:", err);
141
+ } finally {
142
+ resolveTask();
143
+ this.runningTasks.delete(run.id);
144
+ }
145
+ })();
146
+ return queuedSnapshot;
147
+ }
148
+ async streamRun(input, writer) {
149
+ const abortController = new AbortController();
150
+ writer.onClose(() => abortController.abort());
151
+ const { threadId, input: resolvedInput } = await this.ensureThread(input);
152
+ input = resolvedInput;
153
+ const run = await this.store.createRun(input.input.messages, threadId, input.config, input.metadata);
154
+ const rb = RunBuilder.create(run, threadId);
155
+ let resolveTask;
156
+ const taskPromise = new Promise((r) => {
157
+ resolveTask = r;
158
+ });
159
+ this.runningTasks.set(run.id, {
160
+ promise: taskPromise,
161
+ abortController
162
+ });
163
+ writer.writeEvent(AgentSSEEvent.ThreadRunCreated, rb.snapshot());
164
+ await this.store.updateRun(run.id, rb.start());
165
+ writer.writeEvent(AgentSSEEvent.ThreadRunInProgress, rb.snapshot());
166
+ const msgId = newMsgId();
167
+ const msgObj = MessageConverter.createStreamMessage(msgId, run.id);
168
+ writer.writeEvent(AgentSSEEvent.ThreadMessageCreated, msgObj);
169
+ try {
170
+ const { content, usage, aborted } = await this.consumeStreamMessages(input, abortController.signal, writer, msgId);
171
+ if (aborted) {
172
+ rb.cancelling();
173
+ try {
174
+ await this.store.updateRun(run.id, rb.cancel());
175
+ } catch (storeErr) {
176
+ this.logger.error("[AgentRuntime] failed to write cancelled status during stream abort:", storeErr);
177
+ }
178
+ if (!writer.closed) writer.writeEvent(AgentSSEEvent.ThreadRunCancelled, rb.snapshot());
179
+ return;
180
+ }
181
+ const completedMsg = MessageConverter.completeMessage(msgObj, content);
182
+ writer.writeEvent(AgentSSEEvent.ThreadMessageCompleted, completedMsg);
183
+ const output = content.length > 0 ? [completedMsg] : [];
184
+ await this.store.appendMessages(threadId, [...MessageConverter.toInputMessageObjects(input.input.messages, threadId), ...output]);
185
+ await this.store.updateRun(run.id, rb.complete(output, usage));
186
+ writer.writeEvent(AgentSSEEvent.ThreadRunCompleted, rb.snapshot());
187
+ } catch (err) {
188
+ if (abortController.signal.aborted) {
189
+ rb.cancelling();
190
+ try {
191
+ await this.store.updateRun(run.id, rb.cancel());
192
+ } catch (storeErr) {
193
+ this.logger.error("[AgentRuntime] failed to write cancelled status during stream error:", storeErr);
194
+ }
195
+ if (!writer.closed) writer.writeEvent(AgentSSEEvent.ThreadRunCancelled, rb.snapshot());
196
+ } else {
197
+ try {
198
+ await this.store.updateRun(run.id, rb.fail(err));
199
+ } catch (storeErr) {
200
+ this.logger.error("[AgentRuntime] failed to update run status after error:", storeErr);
201
+ }
202
+ if (!writer.closed) writer.writeEvent(AgentSSEEvent.ThreadRunFailed, rb.snapshot());
203
+ }
204
+ } finally {
205
+ resolveTask();
206
+ this.runningTasks.delete(run.id);
207
+ if (!writer.closed) {
208
+ writer.writeEvent(AgentSSEEvent.Done, "[DONE]");
209
+ writer.end();
210
+ }
211
+ }
212
+ }
213
+ /**
214
+ * Consume the execRun async generator, emitting SSE message.delta events
215
+ * for each chunk and accumulating content blocks and token usage.
216
+ */
217
+ async consumeStreamMessages(input, signal, writer, msgId) {
218
+ const content = [];
219
+ let promptTokens = 0;
220
+ let completionTokens = 0;
221
+ let hasUsage = false;
222
+ for await (const msg of this.executor.execRun(input, signal)) {
223
+ if (signal.aborted) return {
224
+ content,
225
+ usage: void 0,
226
+ aborted: true
227
+ };
228
+ if (msg.message) {
229
+ const contentBlocks = MessageConverter.toContentBlocks(msg.message);
230
+ content.push(...contentBlocks);
231
+ const delta = {
232
+ id: msgId,
233
+ object: AgentObjectType.ThreadMessageDelta,
234
+ delta: { content: contentBlocks }
235
+ };
236
+ writer.writeEvent(AgentSSEEvent.ThreadMessageDelta, delta);
237
+ }
238
+ if (msg.usage) {
239
+ hasUsage = true;
240
+ promptTokens += msg.usage.promptTokens ?? 0;
241
+ completionTokens += msg.usage.completionTokens ?? 0;
242
+ }
243
+ }
244
+ return {
245
+ content,
246
+ usage: hasUsage ? {
247
+ promptTokens,
248
+ completionTokens,
249
+ totalTokens: promptTokens + completionTokens
250
+ } : void 0,
251
+ aborted: false
252
+ };
253
+ }
254
+ async getRun(runId) {
255
+ const run = await this.store.getRun(runId);
256
+ return RunBuilder.fromRecord(run).snapshot();
257
+ }
258
+ async cancelRun(runId) {
259
+ const run = await this.store.getRun(runId);
260
+ if (AgentRuntime.TERMINAL_RUN_STATUSES.has(run.status)) throw new AgentConflictError(`Cannot cancel run with status '${run.status}'`);
261
+ const rb = RunBuilder.fromRecord(run);
262
+ await this.store.updateRun(runId, rb.cancelling());
263
+ const task = this.runningTasks.get(runId);
264
+ if (task) {
265
+ task.abortController.abort();
266
+ await task.promise.catch(() => {});
267
+ }
268
+ const freshRun = await this.store.getRun(runId);
269
+ if (AgentRuntime.TERMINAL_RUN_STATUSES.has(freshRun.status)) return RunBuilder.fromRecord(freshRun).snapshot();
270
+ try {
271
+ await this.store.updateRun(runId, rb.cancel());
272
+ } catch (err) {
273
+ this.logger.error("[AgentRuntime] failed to write cancelled state after cancelling:", err);
274
+ const fallback = await this.store.getRun(runId);
275
+ return RunBuilder.fromRecord(fallback).snapshot();
276
+ }
277
+ return rb.snapshot();
278
+ }
279
+ /** Wait for all in-flight background tasks to complete naturally (without aborting). */
280
+ async waitForPendingTasks() {
281
+ if (this.runningTasks.size) {
282
+ const pending = Array.from(this.runningTasks.values()).map((t) => t.promise);
283
+ await Promise.allSettled(pending);
284
+ }
285
+ }
286
+ async destroy() {
287
+ for (const task of this.runningTasks.values()) task.abortController.abort();
288
+ await this.waitForPendingTasks();
289
+ if (this.store.destroy) await this.store.destroy();
290
+ }
291
+ /** Factory method — avoids the spread-arg type issue with dynamic delegation. */
292
+ static create(options) {
293
+ return new AgentRuntime(options);
294
+ }
295
+ };
296
+
297
+ //#endregion
298
+ export { AGENT_RUNTIME, AgentRuntime };
@@ -0,0 +1,7 @@
1
+ //#region src/AgentStoreUtils.d.ts
2
+ declare function nowUnix(): number;
3
+ declare function newMsgId(): string;
4
+ declare function newThreadId(): string;
5
+ declare function newRunId(): string;
6
+ //#endregion
7
+ export { newMsgId, newRunId, newThreadId, nowUnix };
@@ -0,0 +1,18 @@
1
+ import crypto from "node:crypto";
2
+
3
+ //#region src/AgentStoreUtils.ts
4
+ function nowUnix() {
5
+ return Math.floor(Date.now() / 1e3);
6
+ }
7
+ function newMsgId() {
8
+ return `msg_${crypto.randomUUID()}`;
9
+ }
10
+ function newThreadId() {
11
+ return `thread_${crypto.randomUUID()}`;
12
+ }
13
+ function newRunId() {
14
+ return `run_${crypto.randomUUID()}`;
15
+ }
16
+
17
+ //#endregion
18
+ export { newMsgId, newRunId, newThreadId, nowUnix };
@@ -0,0 +1,20 @@
1
+ import { SSEWriter } from "./SSEWriter.js";
2
+ import { ServerResponse } from "node:http";
3
+
4
+ //#region src/HttpSSEWriter.d.ts
5
+ declare class HttpSSEWriter implements SSEWriter {
6
+ private res;
7
+ private _closed;
8
+ private closeCallbacks;
9
+ private headersSent;
10
+ private readonly onResClose;
11
+ constructor(res: ServerResponse);
12
+ /** Lazily write headers on first event — avoids sending corrupt headers if constructor throws. */
13
+ private ensureHeaders;
14
+ writeEvent(event: string, data: unknown): void;
15
+ get closed(): boolean;
16
+ end(): void;
17
+ onClose(callback: () => void): void;
18
+ }
19
+ //#endregion
20
+ export { HttpSSEWriter };
@@ -0,0 +1,49 @@
1
+ //#region src/HttpSSEWriter.ts
2
+ var HttpSSEWriter = class {
3
+ res;
4
+ _closed = false;
5
+ closeCallbacks = [];
6
+ headersSent = false;
7
+ onResClose;
8
+ constructor(res) {
9
+ this.res = res;
10
+ this.onResClose = () => {
11
+ this._closed = true;
12
+ for (const cb of this.closeCallbacks) cb();
13
+ this.closeCallbacks.length = 0;
14
+ };
15
+ res.on("close", this.onResClose);
16
+ }
17
+ /** Lazily write headers on first event — avoids sending corrupt headers if constructor throws. */
18
+ ensureHeaders() {
19
+ if (this.headersSent) return;
20
+ this.headersSent = true;
21
+ this.res.writeHead(200, {
22
+ "content-type": "text/event-stream",
23
+ "cache-control": "no-cache",
24
+ connection: "keep-alive"
25
+ });
26
+ }
27
+ writeEvent(event, data) {
28
+ if (this._closed) return;
29
+ this.ensureHeaders();
30
+ this.res.write(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`);
31
+ }
32
+ get closed() {
33
+ return this._closed;
34
+ }
35
+ end() {
36
+ if (!this._closed) {
37
+ this._closed = true;
38
+ this.res.off("close", this.onResClose);
39
+ this.closeCallbacks.length = 0;
40
+ this.res.end();
41
+ }
42
+ }
43
+ onClose(callback) {
44
+ this.closeCallbacks.push(callback);
45
+ }
46
+ };
47
+
48
+ //#endregion
49
+ export { HttpSSEWriter };
@@ -0,0 +1,36 @@
1
+ import { RunUsage } from "./RunBuilder.js";
2
+ import { AgentStreamMessage, AgentStreamMessagePayload, CreateRunInput, MessageContentBlock, MessageObject } from "@eggjs/tegg-types/agent-runtime";
3
+
4
+ //#region src/MessageConverter.d.ts
5
+ declare class MessageConverter {
6
+ /**
7
+ * Convert an AgentStreamMessage's message payload into OpenAI MessageContentBlock[].
8
+ */
9
+ static toContentBlocks(msg: AgentStreamMessagePayload): MessageContentBlock[];
10
+ /**
11
+ * Build a completed MessageObject from an AgentStreamMessage payload.
12
+ */
13
+ static toMessageObject(msg: AgentStreamMessagePayload, runId?: string): MessageObject;
14
+ /**
15
+ * Extract MessageObjects and accumulated usage from AgentStreamMessage objects.
16
+ */
17
+ static extractFromStreamMessages(messages: AgentStreamMessage[], runId?: string): {
18
+ output: MessageObject[];
19
+ usage?: RunUsage;
20
+ };
21
+ /**
22
+ * Produce a completed copy of a streaming MessageObject with final content.
23
+ */
24
+ static completeMessage(msg: MessageObject, content: MessageContentBlock[]): MessageObject;
25
+ /**
26
+ * Create an in-progress MessageObject for streaming (before content is known).
27
+ */
28
+ static createStreamMessage(msgId: string, runId: string): MessageObject;
29
+ /**
30
+ * Convert input messages to MessageObjects for thread history.
31
+ * System messages are filtered out — they are transient instructions, not conversation history.
32
+ */
33
+ static toInputMessageObjects(messages: CreateRunInput["input"]["messages"], threadId?: string): MessageObject[];
34
+ }
35
+ //#endregion
36
+ export { MessageConverter };
@@ -0,0 +1,123 @@
1
+ import { newMsgId, nowUnix } from "./AgentStoreUtils.js";
2
+ import { AgentObjectType, ContentBlockType, MessageRole, MessageStatus } from "@eggjs/tegg-types/agent-runtime";
3
+
4
+ //#region src/MessageConverter.ts
5
+ var MessageConverter = class MessageConverter {
6
+ /**
7
+ * Convert an AgentStreamMessage's message payload into OpenAI MessageContentBlock[].
8
+ */
9
+ static toContentBlocks(msg) {
10
+ if (!msg) return [];
11
+ const content = msg.content;
12
+ if (typeof content === "string") return [{
13
+ type: ContentBlockType.Text,
14
+ text: {
15
+ value: content,
16
+ annotations: []
17
+ }
18
+ }];
19
+ if (Array.isArray(content)) return content.filter((part) => part.type === ContentBlockType.Text).map((part) => ({
20
+ type: ContentBlockType.Text,
21
+ text: {
22
+ value: part.text,
23
+ annotations: []
24
+ }
25
+ }));
26
+ return [];
27
+ }
28
+ /**
29
+ * Build a completed MessageObject from an AgentStreamMessage payload.
30
+ */
31
+ static toMessageObject(msg, runId) {
32
+ return {
33
+ id: newMsgId(),
34
+ object: AgentObjectType.ThreadMessage,
35
+ createdAt: nowUnix(),
36
+ runId,
37
+ role: MessageRole.Assistant,
38
+ status: MessageStatus.Completed,
39
+ content: MessageConverter.toContentBlocks(msg)
40
+ };
41
+ }
42
+ /**
43
+ * Extract MessageObjects and accumulated usage from AgentStreamMessage objects.
44
+ */
45
+ static extractFromStreamMessages(messages, runId) {
46
+ const output = [];
47
+ let promptTokens = 0;
48
+ let completionTokens = 0;
49
+ let hasUsage = false;
50
+ for (const msg of messages) {
51
+ if (msg.message) output.push(MessageConverter.toMessageObject(msg.message, runId));
52
+ if (msg.usage) {
53
+ hasUsage = true;
54
+ promptTokens += msg.usage.promptTokens ?? 0;
55
+ completionTokens += msg.usage.completionTokens ?? 0;
56
+ }
57
+ }
58
+ let usage;
59
+ if (hasUsage) usage = {
60
+ promptTokens,
61
+ completionTokens,
62
+ totalTokens: promptTokens + completionTokens
63
+ };
64
+ return {
65
+ output,
66
+ usage
67
+ };
68
+ }
69
+ /**
70
+ * Produce a completed copy of a streaming MessageObject with final content.
71
+ */
72
+ static completeMessage(msg, content) {
73
+ return {
74
+ ...msg,
75
+ status: MessageStatus.Completed,
76
+ content
77
+ };
78
+ }
79
+ /**
80
+ * Create an in-progress MessageObject for streaming (before content is known).
81
+ */
82
+ static createStreamMessage(msgId, runId) {
83
+ return {
84
+ id: msgId,
85
+ object: AgentObjectType.ThreadMessage,
86
+ createdAt: nowUnix(),
87
+ runId,
88
+ role: MessageRole.Assistant,
89
+ status: MessageStatus.InProgress,
90
+ content: []
91
+ };
92
+ }
93
+ /**
94
+ * Convert input messages to MessageObjects for thread history.
95
+ * System messages are filtered out — they are transient instructions, not conversation history.
96
+ */
97
+ static toInputMessageObjects(messages, threadId) {
98
+ return messages.filter((m) => m.role !== MessageRole.System).map((m) => ({
99
+ id: newMsgId(),
100
+ object: AgentObjectType.ThreadMessage,
101
+ createdAt: nowUnix(),
102
+ threadId,
103
+ role: m.role,
104
+ status: MessageStatus.Completed,
105
+ content: typeof m.content === "string" ? [{
106
+ type: ContentBlockType.Text,
107
+ text: {
108
+ value: m.content,
109
+ annotations: []
110
+ }
111
+ }] : m.content.map((p) => ({
112
+ type: ContentBlockType.Text,
113
+ text: {
114
+ value: p.text,
115
+ annotations: []
116
+ }
117
+ }))
118
+ }));
119
+ }
120
+ };
121
+
122
+ //#endregion
123
+ export { MessageConverter };
@@ -0,0 +1,66 @@
1
+ import { AgentRunConfig, AgentStore, InputMessage, MessageObject, ObjectStorageClient, RunRecord, ThreadRecord } from "@eggjs/tegg-types/agent-runtime";
2
+
3
+ //#region src/OSSAgentStore.d.ts
4
+ interface OSSAgentStoreOptions {
5
+ client: ObjectStorageClient;
6
+ prefix?: string;
7
+ }
8
+ /**
9
+ * AgentStore implementation backed by an ObjectStorageClient (OSS, S3, etc.).
10
+ *
11
+ * ## Storage layout
12
+ *
13
+ * ```
14
+ * {prefix}threads/{id}/meta.json — Thread metadata (JSON)
15
+ * {prefix}threads/{id}/messages.jsonl — Messages (JSONL, one JSON object per line)
16
+ * {prefix}runs/{id}.json — Run record (JSON)
17
+ * ```
18
+ *
19
+ * ### Why split threads into two keys?
20
+ *
21
+ * Thread messages are append-only: new messages are added at the end but never
22
+ * modified or deleted. Storing them as a JSONL file allows us to leverage the
23
+ * OSS AppendObject API (or similar) to write new messages without reading the
24
+ * entire thread first. This is much more efficient than read-modify-write for
25
+ * long conversations.
26
+ *
27
+ * If the underlying ObjectStorageClient provides an `append()` method, it will
28
+ * be used for O(1) message writes. Otherwise, the store falls back to
29
+ * get-concat-put (which is NOT atomic and may lose data under concurrent
30
+ * writers — acceptable for single-writer scenarios).
31
+ *
32
+ * ### Atomicity note
33
+ *
34
+ * Run updates still use read-modify-write because run fields are mutated
35
+ * (status, timestamps, output, etc.) — they cannot be modelled as append-only.
36
+ * For multi-writer safety, consider a database-backed AgentStore or ETag-based
37
+ * conditional writes with retry.
38
+ */
39
+ declare class OSSAgentStore implements AgentStore {
40
+ private readonly client;
41
+ private readonly prefix;
42
+ constructor(options: OSSAgentStoreOptions);
43
+ /** Key for thread metadata (JSON). */
44
+ private threadMetaKey;
45
+ /** Key for thread messages (JSONL, one message per line). */
46
+ private threadMessagesKey;
47
+ /** Key for run record (JSON). */
48
+ private runKey;
49
+ init(): Promise<void>;
50
+ destroy(): Promise<void>;
51
+ createThread(metadata?: Record<string, unknown>): Promise<ThreadRecord>;
52
+ getThread(threadId: string): Promise<ThreadRecord>;
53
+ /**
54
+ * Append messages to a thread.
55
+ *
56
+ * Each message is serialized as a single JSON line (JSONL format).
57
+ * When the underlying client supports `append()`, this is a single
58
+ * O(1) write — no need to read the existing messages first.
59
+ */
60
+ appendMessages(threadId: string, messages: MessageObject[]): Promise<void>;
61
+ createRun(input: InputMessage[], threadId?: string, config?: AgentRunConfig, metadata?: Record<string, unknown>): Promise<RunRecord>;
62
+ getRun(runId: string): Promise<RunRecord>;
63
+ updateRun(runId: string, updates: Partial<RunRecord>): Promise<void>;
64
+ }
65
+ //#endregion
66
+ export { OSSAgentStore, OSSAgentStoreOptions };