@librechat/agents 3.1.85 → 3.1.87
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 +69 -0
- package/dist/cjs/agents/AgentContext.cjs +7 -2
- package/dist/cjs/agents/AgentContext.cjs.map +1 -1
- package/dist/cjs/events.cjs +23 -0
- package/dist/cjs/events.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +133 -18
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/graphs/MultiAgentGraph.cjs +1 -1
- package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/index.cjs +251 -53
- package/dist/cjs/llm/anthropic/index.cjs.map +1 -1
- package/dist/cjs/llm/init.cjs +1 -5
- package/dist/cjs/llm/init.cjs.map +1 -1
- package/dist/cjs/llm/openai/index.cjs +113 -24
- package/dist/cjs/llm/openai/index.cjs.map +1 -1
- package/dist/cjs/llm/openai/utils/index.cjs.map +1 -1
- package/dist/cjs/llm/openrouter/index.cjs +3 -1
- package/dist/cjs/llm/openrouter/index.cjs.map +1 -1
- package/dist/cjs/main.cjs +18 -5
- package/dist/cjs/main.cjs.map +1 -1
- package/dist/cjs/openai/index.cjs +253 -0
- package/dist/cjs/openai/index.cjs.map +1 -0
- package/dist/cjs/responses/index.cjs +448 -0
- package/dist/cjs/responses/index.cjs.map +1 -0
- package/dist/cjs/run.cjs +108 -7
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/session/AgentSession.cjs +1057 -0
- package/dist/cjs/session/AgentSession.cjs.map +1 -0
- package/dist/cjs/session/JsonlSessionStore.cjs +425 -0
- package/dist/cjs/session/JsonlSessionStore.cjs.map +1 -0
- package/dist/cjs/session/handlers.cjs +221 -0
- package/dist/cjs/session/handlers.cjs.map +1 -0
- package/dist/cjs/session/ids.cjs +22 -0
- package/dist/cjs/session/ids.cjs.map +1 -0
- package/dist/cjs/session/messageSerialization.cjs +179 -0
- package/dist/cjs/session/messageSerialization.cjs.map +1 -0
- package/dist/cjs/stream.cjs +472 -11
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/cjs/summarization/node.cjs +1 -1
- package/dist/cjs/summarization/node.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +177 -59
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/eagerEventExecution.cjs +113 -0
- package/dist/cjs/tools/eagerEventExecution.cjs.map +1 -0
- package/dist/cjs/tools/handlers.cjs +1 -1
- package/dist/cjs/tools/handlers.cjs.map +1 -1
- package/dist/cjs/tools/streamedToolCallSeals.cjs +42 -0
- package/dist/cjs/tools/streamedToolCallSeals.cjs.map +1 -0
- package/dist/esm/agents/AgentContext.mjs +7 -2
- package/dist/esm/agents/AgentContext.mjs.map +1 -1
- package/dist/esm/events.mjs +23 -1
- package/dist/esm/events.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +133 -18
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/graphs/MultiAgentGraph.mjs +1 -1
- package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
- package/dist/esm/llm/anthropic/index.mjs +251 -53
- package/dist/esm/llm/anthropic/index.mjs.map +1 -1
- package/dist/esm/llm/init.mjs +1 -5
- package/dist/esm/llm/init.mjs.map +1 -1
- package/dist/esm/llm/openai/index.mjs +113 -25
- package/dist/esm/llm/openai/index.mjs.map +1 -1
- package/dist/esm/llm/openai/utils/index.mjs.map +1 -1
- package/dist/esm/llm/openrouter/index.mjs +4 -2
- package/dist/esm/llm/openrouter/index.mjs.map +1 -1
- package/dist/esm/main.mjs +5 -1
- package/dist/esm/main.mjs.map +1 -1
- package/dist/esm/openai/index.mjs +246 -0
- package/dist/esm/openai/index.mjs.map +1 -0
- package/dist/esm/responses/index.mjs +440 -0
- package/dist/esm/responses/index.mjs.map +1 -0
- package/dist/esm/run.mjs +108 -7
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/session/AgentSession.mjs +1054 -0
- package/dist/esm/session/AgentSession.mjs.map +1 -0
- package/dist/esm/session/JsonlSessionStore.mjs +422 -0
- package/dist/esm/session/JsonlSessionStore.mjs.map +1 -0
- package/dist/esm/session/handlers.mjs +219 -0
- package/dist/esm/session/handlers.mjs.map +1 -0
- package/dist/esm/session/ids.mjs +17 -0
- package/dist/esm/session/ids.mjs.map +1 -0
- package/dist/esm/session/messageSerialization.mjs +173 -0
- package/dist/esm/session/messageSerialization.mjs.map +1 -0
- package/dist/esm/stream.mjs +473 -12
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/esm/summarization/node.mjs +1 -1
- package/dist/esm/summarization/node.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +177 -59
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/eagerEventExecution.mjs +107 -0
- package/dist/esm/tools/eagerEventExecution.mjs.map +1 -0
- package/dist/esm/tools/handlers.mjs +1 -1
- package/dist/esm/tools/handlers.mjs.map +1 -1
- package/dist/esm/tools/streamedToolCallSeals.mjs +36 -0
- package/dist/esm/tools/streamedToolCallSeals.mjs.map +1 -0
- package/dist/types/events.d.ts +1 -0
- package/dist/types/graphs/Graph.d.ts +24 -9
- package/dist/types/index.d.ts +1 -0
- package/dist/types/llm/openai/index.d.ts +1 -0
- package/dist/types/openai/index.d.ts +75 -0
- package/dist/types/responses/index.d.ts +97 -0
- package/dist/types/run.d.ts +2 -0
- package/dist/types/session/AgentSession.d.ts +32 -0
- package/dist/types/session/JsonlSessionStore.d.ts +67 -0
- package/dist/types/session/handlers.d.ts +8 -0
- package/dist/types/session/ids.d.ts +4 -0
- package/dist/types/session/index.d.ts +5 -0
- package/dist/types/session/messageSerialization.d.ts +7 -0
- package/dist/types/session/types.d.ts +191 -0
- package/dist/types/tools/ToolNode.d.ts +12 -1
- package/dist/types/tools/eagerEventExecution.d.ts +23 -0
- package/dist/types/tools/streamedToolCallSeals.d.ts +13 -0
- package/dist/types/types/hitl.d.ts +4 -0
- package/dist/types/types/run.d.ts +11 -1
- package/dist/types/types/tools.d.ts +36 -0
- package/package.json +19 -2
- package/src/__tests__/stream.eagerEventExecution.test.ts +2458 -0
- package/src/agents/AgentContext.ts +7 -2
- package/src/agents/__tests__/AgentContext.test.ts +254 -5
- package/src/events.ts +29 -0
- package/src/graphs/Graph.ts +224 -50
- package/src/graphs/MultiAgentGraph.ts +1 -1
- package/src/graphs/__tests__/composition.smoke.test.ts +30 -0
- package/src/index.ts +3 -0
- package/src/llm/anthropic/index.ts +356 -84
- package/src/llm/anthropic/llm.spec.ts +64 -0
- package/src/llm/custom-chat-models.smoke.test.ts +175 -4
- package/src/llm/openai/contentBlocks.test.ts +35 -0
- package/src/llm/openai/deepseek.test.ts +201 -2
- package/src/llm/openai/index.ts +171 -26
- package/src/llm/openai/utils/index.ts +22 -0
- package/src/llm/openrouter/index.ts +4 -2
- package/src/openai/__tests__/openai.test.ts +337 -0
- package/src/openai/index.ts +404 -0
- package/src/responses/__tests__/responses.test.ts +652 -0
- package/src/responses/index.ts +677 -0
- package/src/run.ts +158 -8
- package/src/scripts/compare_pi_vs_ours.ts +592 -173
- package/src/scripts/session_live.ts +548 -0
- package/src/session/AgentSession.ts +1432 -0
- package/src/session/JsonlSessionStore.ts +572 -0
- package/src/session/__tests__/JsonlSessionStore.test.ts +1410 -0
- package/src/session/__tests__/handlers.test.ts +161 -0
- package/src/session/handlers.ts +272 -0
- package/src/session/ids.ts +17 -0
- package/src/session/index.ts +44 -0
- package/src/session/messageSerialization.ts +207 -0
- package/src/session/types.ts +275 -0
- package/src/specs/custom-event-await.test.ts +89 -0
- package/src/specs/summarization.test.ts +1 -1
- package/src/stream.ts +755 -48
- package/src/summarization/node.ts +1 -1
- package/src/tools/ToolNode.ts +299 -126
- package/src/tools/__tests__/ToolNode.eagerEventExecution.test.ts +373 -0
- package/src/tools/__tests__/handlers.test.ts +2 -1
- package/src/tools/__tests__/hitl.test.ts +206 -110
- package/src/tools/eagerEventExecution.ts +153 -0
- package/src/tools/handlers.ts +8 -4
- package/src/tools/streamedToolCallSeals.ts +57 -0
- package/src/types/hitl.ts +4 -0
- package/src/types/run.ts +11 -0
- package/src/types/tools.ts +36 -0
- package/dist/cjs/llm/text.cjs +0 -69
- package/dist/cjs/llm/text.cjs.map +0 -1
- package/dist/esm/llm/text.mjs +0 -67
- package/dist/esm/llm/text.mjs.map +0 -1
|
@@ -0,0 +1,548 @@
|
|
|
1
|
+
import { config } from 'dotenv';
|
|
2
|
+
import { existsSync } from 'fs';
|
|
3
|
+
import { mkdtemp } from 'fs/promises';
|
|
4
|
+
import { tmpdir } from 'os';
|
|
5
|
+
import { join } from 'path';
|
|
6
|
+
import type { BaseMessage } from '@langchain/core/messages';
|
|
7
|
+
import type {
|
|
8
|
+
AgentSession,
|
|
9
|
+
AgentSessionRunResult,
|
|
10
|
+
SessionMessageEntry,
|
|
11
|
+
} from '@/session';
|
|
12
|
+
import type * as t from '@/types';
|
|
13
|
+
import {
|
|
14
|
+
createOpenAIHandlers,
|
|
15
|
+
createOpenAIStreamTracker,
|
|
16
|
+
sendOpenAIFinalChunk,
|
|
17
|
+
} from '@/openai';
|
|
18
|
+
import {
|
|
19
|
+
createResponseTracker,
|
|
20
|
+
createResponsesEventHandlers,
|
|
21
|
+
emitResponseCompleted,
|
|
22
|
+
} from '@/responses';
|
|
23
|
+
import { Constants, GraphEvents, Providers } from '@/common';
|
|
24
|
+
import { createAgentSession } from '@/session';
|
|
25
|
+
import { Calculator } from '@/tools/Calculator';
|
|
26
|
+
import { getLLMConfig } from '@/utils/llmConfig';
|
|
27
|
+
|
|
28
|
+
const DEFAULT_ENV_PATH = '/Users/danny/Projects/agents/.env';
|
|
29
|
+
const DEFAULT_MODEL_BY_PROVIDER: Partial<Record<Providers, string>> = {
|
|
30
|
+
[Providers.OPENAI]: 'gpt-4.1-mini',
|
|
31
|
+
[Providers.ANTHROPIC]: 'claude-haiku-4-5',
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
function getArgValue(name: string): string | undefined {
|
|
35
|
+
const index = process.argv.indexOf(name);
|
|
36
|
+
if (index === -1 || index + 1 >= process.argv.length) {
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
return process.argv[index + 1];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const envPath =
|
|
43
|
+
getArgValue('--env') ?? process.env.LIVE_ENV_PATH ?? DEFAULT_ENV_PATH;
|
|
44
|
+
|
|
45
|
+
if (existsSync(envPath)) {
|
|
46
|
+
config({ path: envPath });
|
|
47
|
+
}
|
|
48
|
+
config();
|
|
49
|
+
|
|
50
|
+
function assertLive(condition: unknown, message: string): asserts condition {
|
|
51
|
+
if (!condition) {
|
|
52
|
+
throw new Error(`Live session smoke failed: ${message}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function normalizeProvider(value: string | undefined): Providers | undefined {
|
|
57
|
+
if (value == null || value === '') {
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
if (value === Providers.ANTHROPIC || value.toLowerCase() === 'anthropic') {
|
|
61
|
+
return Providers.ANTHROPIC;
|
|
62
|
+
}
|
|
63
|
+
if (
|
|
64
|
+
value === Providers.OPENAI ||
|
|
65
|
+
value.toLowerCase() === 'openai' ||
|
|
66
|
+
value.toLowerCase() === 'openai'
|
|
67
|
+
) {
|
|
68
|
+
return Providers.OPENAI;
|
|
69
|
+
}
|
|
70
|
+
throw new Error(`Unsupported live provider: ${value}`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function resolveProvider(): Providers {
|
|
74
|
+
const requested = normalizeProvider(
|
|
75
|
+
getArgValue('--provider') ?? process.env.LIVE_PROVIDER
|
|
76
|
+
);
|
|
77
|
+
if (requested) {
|
|
78
|
+
return requested;
|
|
79
|
+
}
|
|
80
|
+
if (process.env.ANTHROPIC_API_KEY) {
|
|
81
|
+
return Providers.ANTHROPIC;
|
|
82
|
+
}
|
|
83
|
+
if (process.env.OPENAI_API_KEY) {
|
|
84
|
+
return Providers.OPENAI;
|
|
85
|
+
}
|
|
86
|
+
throw new Error(
|
|
87
|
+
'Missing ANTHROPIC_API_KEY or OPENAI_API_KEY. Pass --env or LIVE_ENV_PATH.'
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function apiKeyForProvider(provider: Providers): string {
|
|
92
|
+
const envName =
|
|
93
|
+
provider === Providers.ANTHROPIC ? 'ANTHROPIC_API_KEY' : 'OPENAI_API_KEY';
|
|
94
|
+
const apiKey = process.env[envName];
|
|
95
|
+
if (apiKey == null || apiKey === '') {
|
|
96
|
+
throw new Error(`Missing ${envName} for provider ${provider}`);
|
|
97
|
+
}
|
|
98
|
+
return apiKey;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function createLiveLLMConfig(provider: Providers): t.LLMConfig {
|
|
102
|
+
const apiKey = apiKeyForProvider(provider);
|
|
103
|
+
const model =
|
|
104
|
+
getArgValue('--model') ??
|
|
105
|
+
process.env.LIVE_MODEL ??
|
|
106
|
+
DEFAULT_MODEL_BY_PROVIDER[provider] ??
|
|
107
|
+
getLLMConfig(provider).model;
|
|
108
|
+
const openAIFields =
|
|
109
|
+
provider === Providers.OPENAI ? { openAIApiKey: apiKey } : {};
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
...getLLMConfig(provider),
|
|
113
|
+
...openAIFields,
|
|
114
|
+
apiKey,
|
|
115
|
+
model,
|
|
116
|
+
modelName: model,
|
|
117
|
+
streaming: true,
|
|
118
|
+
streamUsage: true,
|
|
119
|
+
temperature: 0,
|
|
120
|
+
} as t.LLMConfig;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function createAgentInputs(params: {
|
|
124
|
+
agentId: string;
|
|
125
|
+
provider: Providers;
|
|
126
|
+
llmConfig: t.LLMConfig;
|
|
127
|
+
instructions: string;
|
|
128
|
+
}): t.AgentInputs {
|
|
129
|
+
const { provider: _provider, ...clientOptions } = params.llmConfig;
|
|
130
|
+
return {
|
|
131
|
+
agentId: params.agentId,
|
|
132
|
+
provider: params.provider,
|
|
133
|
+
clientOptions: clientOptions as t.ClientOptions,
|
|
134
|
+
instructions: params.instructions,
|
|
135
|
+
maxContextTokens: 8000,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function contentToText(content: BaseMessage['content']): string {
|
|
140
|
+
if (typeof content === 'string') {
|
|
141
|
+
return content;
|
|
142
|
+
}
|
|
143
|
+
const chunks: string[] = [];
|
|
144
|
+
for (const part of content) {
|
|
145
|
+
if (typeof part === 'string') {
|
|
146
|
+
chunks.push(part);
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
if (part.type === 'text' && typeof part.text === 'string') {
|
|
150
|
+
chunks.push(part.text);
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
if ('think' in part && typeof part.think === 'string') {
|
|
154
|
+
chunks.push(part.think);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return chunks.join('');
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function resultText(result: AgentSessionRunResult): string {
|
|
161
|
+
if (result.text.trim() !== '') {
|
|
162
|
+
return result.text.trim();
|
|
163
|
+
}
|
|
164
|
+
const aiMessage = [...result.messages]
|
|
165
|
+
.reverse()
|
|
166
|
+
.find((message) => message._getType() === 'ai');
|
|
167
|
+
return aiMessage ? contentToText(aiMessage.content).trim() : '';
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function resultMessagesText(result: AgentSessionRunResult): string {
|
|
171
|
+
return result.messages
|
|
172
|
+
.map((message) => contentToText(message.content))
|
|
173
|
+
.filter((text) => text.trim() !== '')
|
|
174
|
+
.join('\n')
|
|
175
|
+
.trim();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function preview(text: string): string {
|
|
179
|
+
return text.replace(/\s+/g, ' ').slice(0, 180);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function logPass(label: string, detail: string): void {
|
|
183
|
+
console.log(`[PASS] ${label}: ${detail}`);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function messageName(message: BaseMessage): string | undefined {
|
|
187
|
+
return (message as BaseMessage & { name?: string }).name;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function hasSubagentToolMessage(messages: BaseMessage[]): boolean {
|
|
191
|
+
return messages.some(
|
|
192
|
+
(message) =>
|
|
193
|
+
message._getType() === 'tool' &&
|
|
194
|
+
messageName(message) === Constants.SUBAGENT
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function getStore(
|
|
199
|
+
session: AgentSession
|
|
200
|
+
): NonNullable<ReturnType<AgentSession['getSessionStore']>> {
|
|
201
|
+
const store = session.getSessionStore();
|
|
202
|
+
assertLive(store != null, 'expected JSONL-backed session store');
|
|
203
|
+
return store;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async function runAdapterSmoke(): Promise<void> {
|
|
207
|
+
const openAIWrites: string[] = [];
|
|
208
|
+
const openAITracker = createOpenAIStreamTracker();
|
|
209
|
+
const openAIHandlers = createOpenAIHandlers({
|
|
210
|
+
writer: { write: (data) => void openAIWrites.push(data) },
|
|
211
|
+
context: { requestId: 'chatcmpl_live', model: 'agent-live', created: 1 },
|
|
212
|
+
tracker: openAITracker,
|
|
213
|
+
});
|
|
214
|
+
await openAIHandlers[GraphEvents.ON_MESSAGE_DELTA].handle(
|
|
215
|
+
GraphEvents.ON_MESSAGE_DELTA,
|
|
216
|
+
{
|
|
217
|
+
id: 'msg_live',
|
|
218
|
+
delta: { content: [{ type: 'text', text: 'adapter text' }] },
|
|
219
|
+
} satisfies t.MessageDeltaEvent
|
|
220
|
+
);
|
|
221
|
+
await sendOpenAIFinalChunk({
|
|
222
|
+
writer: { write: (data) => void openAIWrites.push(data) },
|
|
223
|
+
context: { requestId: 'chatcmpl_live', model: 'agent-live', created: 1 },
|
|
224
|
+
tracker: openAITracker,
|
|
225
|
+
});
|
|
226
|
+
assertLive(
|
|
227
|
+
openAIWrites.join('').includes('"content":"adapter text"'),
|
|
228
|
+
'OpenAI-compatible adapter did not stream text'
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
const responseWrites: string[] = [];
|
|
232
|
+
const responseTracker = createResponseTracker();
|
|
233
|
+
const responseHandlers = createResponsesEventHandlers({
|
|
234
|
+
writer: { write: (data) => void responseWrites.push(data) },
|
|
235
|
+
context: { responseId: 'resp_live', model: 'agent-live', createdAt: 1 },
|
|
236
|
+
tracker: responseTracker,
|
|
237
|
+
});
|
|
238
|
+
await responseHandlers[GraphEvents.ON_MESSAGE_DELTA].handle(
|
|
239
|
+
GraphEvents.ON_MESSAGE_DELTA,
|
|
240
|
+
{
|
|
241
|
+
id: 'msg_live',
|
|
242
|
+
delta: { content: [{ type: 'text', text: 'response text' }] },
|
|
243
|
+
} satisfies t.MessageDeltaEvent
|
|
244
|
+
);
|
|
245
|
+
await emitResponseCompleted({
|
|
246
|
+
writer: { write: (data) => void responseWrites.push(data) },
|
|
247
|
+
context: { responseId: 'resp_live', model: 'agent-live', createdAt: 1 },
|
|
248
|
+
tracker: responseTracker,
|
|
249
|
+
});
|
|
250
|
+
assertLive(
|
|
251
|
+
responseWrites.join('').includes('response.completed'),
|
|
252
|
+
'Responses-compatible adapter did not complete'
|
|
253
|
+
);
|
|
254
|
+
logPass('adapter smoke', 'OpenAI chat and Responses writers emitted events');
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
async function runSessionLifecycleSmoke(params: {
|
|
258
|
+
root: string;
|
|
259
|
+
provider: Providers;
|
|
260
|
+
llmConfig: t.LLMConfig;
|
|
261
|
+
}): Promise<void> {
|
|
262
|
+
const basePath = join(params.root, 'base-session.jsonl');
|
|
263
|
+
const session = await createAgentSession({
|
|
264
|
+
cwd: process.cwd(),
|
|
265
|
+
sessionPath: basePath,
|
|
266
|
+
name: 'live-session-base',
|
|
267
|
+
checkpointing: true,
|
|
268
|
+
graphConfig: {
|
|
269
|
+
type: 'standard',
|
|
270
|
+
llmConfig: params.llmConfig,
|
|
271
|
+
instructions:
|
|
272
|
+
'You are a concise SDK smoke-test assistant. Obey exact output-token requests.',
|
|
273
|
+
tools: [new Calculator()],
|
|
274
|
+
maxContextTokens: 8000,
|
|
275
|
+
},
|
|
276
|
+
returnContent: true,
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
const first = await session.run(
|
|
280
|
+
'Use the calculator tool to compute (18 * 7) + 13. Include the token SESSION_BASE_OK.'
|
|
281
|
+
);
|
|
282
|
+
const firstText = resultText(first);
|
|
283
|
+
assertLive(firstText.includes('SESSION_BASE_OK'), 'base run marker missing');
|
|
284
|
+
assertLive(firstText.includes('139'), 'calculator result missing');
|
|
285
|
+
logPass('standard session run', preview(firstText));
|
|
286
|
+
|
|
287
|
+
const store = getStore(session);
|
|
288
|
+
const firstCheckpoint = await session.getLatestCheckpoint();
|
|
289
|
+
assertLive(
|
|
290
|
+
firstCheckpoint?.threadId === session.threadId,
|
|
291
|
+
'LangGraph checkpoint reference missing after run'
|
|
292
|
+
);
|
|
293
|
+
assertLive(
|
|
294
|
+
store.getCheckpoints(session.threadId).length > 0,
|
|
295
|
+
'JSONL checkpoint journal entry missing'
|
|
296
|
+
);
|
|
297
|
+
logPass(
|
|
298
|
+
'LangGraph checkpoint state',
|
|
299
|
+
firstCheckpoint.checkpointId ?? 'latest'
|
|
300
|
+
);
|
|
301
|
+
|
|
302
|
+
const forkPoint = store.getForkPoints()[0];
|
|
303
|
+
assertLive(forkPoint != null, 'no user fork point recorded');
|
|
304
|
+
await store.setLabel(forkPoint.id, 'first user turn');
|
|
305
|
+
assertLive(
|
|
306
|
+
store.getLabel(forkPoint.id) === 'first user turn',
|
|
307
|
+
'label round-trip failed'
|
|
308
|
+
);
|
|
309
|
+
logPass(
|
|
310
|
+
'JSONL persistence',
|
|
311
|
+
`${store.getEntries().length} entries at ${store.path}`
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
const clone = await session.clone({
|
|
315
|
+
cwd: params.root,
|
|
316
|
+
name: 'live-session-clone',
|
|
317
|
+
});
|
|
318
|
+
const cloneResult = await clone.run(
|
|
319
|
+
'Continue this cloned session and include the token SESSION_CLONE_OK.'
|
|
320
|
+
);
|
|
321
|
+
assertLive(
|
|
322
|
+
resultText(cloneResult).includes('SESSION_CLONE_OK'),
|
|
323
|
+
'clone run marker missing'
|
|
324
|
+
);
|
|
325
|
+
logPass('clone', getStore(clone).path);
|
|
326
|
+
|
|
327
|
+
const forkBefore = await session.fork(forkPoint.id, {
|
|
328
|
+
cwd: params.root,
|
|
329
|
+
name: 'live-session-fork-before',
|
|
330
|
+
position: 'before',
|
|
331
|
+
});
|
|
332
|
+
const forkResult = await forkBefore.run(
|
|
333
|
+
'This replacement branch should not know the prior arithmetic. Include SESSION_FORK_OK.'
|
|
334
|
+
);
|
|
335
|
+
assertLive(
|
|
336
|
+
resultText(forkResult).includes('SESSION_FORK_OK'),
|
|
337
|
+
'fork-before run marker missing'
|
|
338
|
+
);
|
|
339
|
+
logPass('fork before entry', getStore(forkBefore).path);
|
|
340
|
+
|
|
341
|
+
await session.branch(forkPoint.id, {
|
|
342
|
+
position: 'before',
|
|
343
|
+
summarizeAbandoned: {
|
|
344
|
+
instructions: 'Summary of abandoned live branch before branch switch.',
|
|
345
|
+
},
|
|
346
|
+
});
|
|
347
|
+
const branchResult = await session.run(
|
|
348
|
+
'This is an in-place alternate branch. Include SESSION_BRANCH_OK.'
|
|
349
|
+
);
|
|
350
|
+
assertLive(
|
|
351
|
+
resultText(branchResult).includes('SESSION_BRANCH_OK'),
|
|
352
|
+
'branch run marker missing'
|
|
353
|
+
);
|
|
354
|
+
const branchStore = getStore(session);
|
|
355
|
+
assertLive(
|
|
356
|
+
branchStore.getEntries().some((entry) => entry.type === 'compaction'),
|
|
357
|
+
'branch abandoned-summary compaction entry missing'
|
|
358
|
+
);
|
|
359
|
+
logPass('branch in place', `${branchStore.getTree().length} root branch(es)`);
|
|
360
|
+
|
|
361
|
+
await session.compact({
|
|
362
|
+
instructions:
|
|
363
|
+
'Write a concise checkpoint summary of this live session branch.',
|
|
364
|
+
retainRecentTurns: 0,
|
|
365
|
+
});
|
|
366
|
+
const compactedMessages = branchStore.getMessages();
|
|
367
|
+
assertLive(
|
|
368
|
+
compactedMessages[0]?._getType() === 'system' &&
|
|
369
|
+
typeof compactedMessages[0].content === 'string' &&
|
|
370
|
+
compactedMessages[0].content.trim() !== '',
|
|
371
|
+
'manual compaction summary not active'
|
|
372
|
+
);
|
|
373
|
+
assertLive(
|
|
374
|
+
branchStore
|
|
375
|
+
.getCheckpoints(session.threadId)
|
|
376
|
+
.some((entry) => entry.data.source === 'reset'),
|
|
377
|
+
'checkpoint reset entry missing after compact'
|
|
378
|
+
);
|
|
379
|
+
logPass('manual compact', `${compactedMessages.length} active messages`);
|
|
380
|
+
|
|
381
|
+
const resumed = await createAgentSession({
|
|
382
|
+
cwd: process.cwd(),
|
|
383
|
+
ephemeral: true,
|
|
384
|
+
graphConfig: {
|
|
385
|
+
type: 'standard',
|
|
386
|
+
llmConfig: params.llmConfig,
|
|
387
|
+
instructions:
|
|
388
|
+
'You are a concise SDK smoke-test assistant. Obey exact output-token requests.',
|
|
389
|
+
maxContextTokens: 8000,
|
|
390
|
+
},
|
|
391
|
+
returnContent: true,
|
|
392
|
+
});
|
|
393
|
+
await resumed.resumeSession(basePath);
|
|
394
|
+
assertLive(
|
|
395
|
+
getStore(resumed).getMessages().length === branchStore.getMessages().length,
|
|
396
|
+
'resumeSession did not restore active message path'
|
|
397
|
+
);
|
|
398
|
+
|
|
399
|
+
const stream = resumed.stream(
|
|
400
|
+
'Reply with the exact token SESSION_STREAM_OK and no extra words.'
|
|
401
|
+
);
|
|
402
|
+
let streamedText = '';
|
|
403
|
+
for await (const chunk of stream.toTextStream()) {
|
|
404
|
+
streamedText += chunk;
|
|
405
|
+
}
|
|
406
|
+
const streamResult = await stream.finalResult();
|
|
407
|
+
const finalStreamText = streamedText || resultText(streamResult);
|
|
408
|
+
assertLive(
|
|
409
|
+
finalStreamText.includes('SESSION_STREAM_OK'),
|
|
410
|
+
'stream helper marker missing'
|
|
411
|
+
);
|
|
412
|
+
logPass('resume and stream helpers', preview(finalStreamText));
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
async function runMultiAgentSmoke(params: {
|
|
416
|
+
root: string;
|
|
417
|
+
provider: Providers;
|
|
418
|
+
llmConfig: t.LLMConfig;
|
|
419
|
+
}): Promise<void> {
|
|
420
|
+
const agents: t.AgentInputs[] = [
|
|
421
|
+
createAgentInputs({
|
|
422
|
+
agentId: 'session_architect',
|
|
423
|
+
provider: params.provider,
|
|
424
|
+
llmConfig: params.llmConfig,
|
|
425
|
+
instructions:
|
|
426
|
+
'You are Agent A. Start with AGENT_A and give one JSONL session-tree DX benefit in one sentence.',
|
|
427
|
+
}),
|
|
428
|
+
createAgentInputs({
|
|
429
|
+
agentId: 'session_reviewer',
|
|
430
|
+
provider: params.provider,
|
|
431
|
+
llmConfig: params.llmConfig,
|
|
432
|
+
instructions:
|
|
433
|
+
'You are Agent B. Start with AGENT_B and add one implementation caution. End with MULTI_AGENT_OK.',
|
|
434
|
+
}),
|
|
435
|
+
];
|
|
436
|
+
const result = await (
|
|
437
|
+
await createAgentSession({
|
|
438
|
+
cwd: process.cwd(),
|
|
439
|
+
sessionPath: join(params.root, 'multi-agent-session.jsonl'),
|
|
440
|
+
name: 'live-session-multi-agent',
|
|
441
|
+
graphConfig: {
|
|
442
|
+
type: 'multi-agent',
|
|
443
|
+
agents,
|
|
444
|
+
edges: [
|
|
445
|
+
{
|
|
446
|
+
from: 'session_architect',
|
|
447
|
+
to: 'session_reviewer',
|
|
448
|
+
edgeType: 'direct',
|
|
449
|
+
description: 'Review the architect output',
|
|
450
|
+
},
|
|
451
|
+
],
|
|
452
|
+
},
|
|
453
|
+
returnContent: true,
|
|
454
|
+
})
|
|
455
|
+
).run('Run the two-agent SDK session DX review.');
|
|
456
|
+
|
|
457
|
+
const aiCount = result.messages.filter(
|
|
458
|
+
(message) => message._getType() === 'ai'
|
|
459
|
+
).length;
|
|
460
|
+
const text = resultText(result) || resultMessagesText(result);
|
|
461
|
+
assertLive(
|
|
462
|
+
aiCount >= 2,
|
|
463
|
+
'multi-agent direct edge did not produce two AI turns'
|
|
464
|
+
);
|
|
465
|
+
assertLive(text !== '', 'multi-agent graph produced empty text');
|
|
466
|
+
logPass('multi-agent direct graph', preview(text));
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
async function runSubagentSmoke(params: {
|
|
470
|
+
root: string;
|
|
471
|
+
provider: Providers;
|
|
472
|
+
llmConfig: t.LLMConfig;
|
|
473
|
+
}): Promise<void> {
|
|
474
|
+
const analystConfig = createAgentInputs({
|
|
475
|
+
agentId: 'jsonl_analyst',
|
|
476
|
+
provider: params.provider,
|
|
477
|
+
llmConfig: params.llmConfig,
|
|
478
|
+
instructions:
|
|
479
|
+
'You are an isolated JSONL analyst. Give exactly two concise tradeoffs and include SUBAGENT_CHILD_OK.',
|
|
480
|
+
});
|
|
481
|
+
const supervisor = createAgentInputs({
|
|
482
|
+
agentId: 'supervisor',
|
|
483
|
+
provider: params.provider,
|
|
484
|
+
llmConfig: params.llmConfig,
|
|
485
|
+
instructions:
|
|
486
|
+
'You are a supervisor. You must use the subagent tool exactly once for JSONL tradeoff analysis, then summarize it and include SUBAGENT_PARENT_OK.',
|
|
487
|
+
});
|
|
488
|
+
supervisor.subagentConfigs = [
|
|
489
|
+
{
|
|
490
|
+
type: 'jsonl_analyst',
|
|
491
|
+
name: 'JSONL Analyst',
|
|
492
|
+
description: 'Analyzes JSONL session-tree tradeoffs.',
|
|
493
|
+
agentInputs: analystConfig,
|
|
494
|
+
},
|
|
495
|
+
];
|
|
496
|
+
|
|
497
|
+
const session = await createAgentSession({
|
|
498
|
+
cwd: process.cwd(),
|
|
499
|
+
sessionPath: join(params.root, 'subagent-session.jsonl'),
|
|
500
|
+
name: 'live-session-subagent',
|
|
501
|
+
graphConfig: {
|
|
502
|
+
type: 'standard',
|
|
503
|
+
agents: [supervisor],
|
|
504
|
+
},
|
|
505
|
+
returnContent: true,
|
|
506
|
+
});
|
|
507
|
+
const result = await session.run(
|
|
508
|
+
'Delegate to jsonl_analyst using the subagent tool. Ask for two tradeoffs of JSONL session trees for CI replay, then summarize.'
|
|
509
|
+
);
|
|
510
|
+
const storedMessages = getStore(session).getMessages();
|
|
511
|
+
assertLive(
|
|
512
|
+
hasSubagentToolMessage(result.messages) ||
|
|
513
|
+
hasSubagentToolMessage(storedMessages),
|
|
514
|
+
'subagent tool message missing'
|
|
515
|
+
);
|
|
516
|
+
const text = resultText(result) || resultMessagesText(result);
|
|
517
|
+
assertLive(text !== '', 'subagent delegation produced empty text');
|
|
518
|
+
logPass('subagent delegation', preview(text));
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
async function main(): Promise<void> {
|
|
522
|
+
const provider = resolveProvider();
|
|
523
|
+
const llmConfig = createLiveLLMConfig(provider);
|
|
524
|
+
const root = await mkdtemp(join(tmpdir(), 'lc-agent-session-live-'));
|
|
525
|
+
console.log('Live AgentSession smoke test');
|
|
526
|
+
console.log(`Provider: ${provider}`);
|
|
527
|
+
console.log(`Model: ${llmConfig.model}`);
|
|
528
|
+
console.log(
|
|
529
|
+
`Env path: ${existsSync(envPath) ? envPath : 'default process env'}`
|
|
530
|
+
);
|
|
531
|
+
console.log(`Artifacts: ${root}\n`);
|
|
532
|
+
|
|
533
|
+
await runAdapterSmoke();
|
|
534
|
+
await runSessionLifecycleSmoke({ root, provider, llmConfig });
|
|
535
|
+
await runMultiAgentSmoke({ root, provider, llmConfig });
|
|
536
|
+
await runSubagentSmoke({ root, provider, llmConfig });
|
|
537
|
+
|
|
538
|
+
console.log('\nAll live session smoke checks passed.');
|
|
539
|
+
console.log(`Session JSONL artifacts kept at: ${root}`);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
main().catch((error: Error) => {
|
|
543
|
+
console.error(error.message);
|
|
544
|
+
if (error.stack) {
|
|
545
|
+
console.error(error.stack);
|
|
546
|
+
}
|
|
547
|
+
process.exitCode = 1;
|
|
548
|
+
});
|