@roj-ai/sdk 0.1.28 → 0.1.30
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/dist/core/agents/agent.d.ts +28 -0
- package/dist/core/agents/agent.d.ts.map +1 -1
- package/dist/core/agents/agent.js +116 -39
- package/dist/core/agents/agent.js.map +1 -1
- package/dist/core/agents/config.d.ts +9 -0
- package/dist/core/agents/config.d.ts.map +1 -1
- package/dist/core/agents/state.d.ts +9 -0
- package/dist/core/agents/state.d.ts.map +1 -1
- package/dist/core/agents/state.js +4 -0
- package/dist/core/agents/state.js.map +1 -1
- package/dist/core/context/state.d.ts +22 -13
- package/dist/core/context/state.d.ts.map +1 -1
- package/dist/core/context/state.js +78 -22
- package/dist/core/context/state.js.map +1 -1
- package/dist/core/error-recovery.integration.test.d.ts +2 -0
- package/dist/core/error-recovery.integration.test.d.ts.map +1 -0
- package/dist/core/error-recovery.integration.test.js +288 -0
- package/dist/core/error-recovery.integration.test.js.map +1 -0
- package/dist/core/llm/anthropic.d.ts.map +1 -1
- package/dist/core/llm/anthropic.js +6 -1
- package/dist/core/llm/anthropic.js.map +1 -1
- package/dist/core/llm/anthropic.test.js +7 -4
- package/dist/core/llm/anthropic.test.js.map +1 -1
- package/dist/core/llm/message-sanitization.d.ts +10 -0
- package/dist/core/llm/message-sanitization.d.ts.map +1 -0
- package/dist/core/llm/message-sanitization.js +53 -0
- package/dist/core/llm/message-sanitization.js.map +1 -0
- package/dist/core/llm/message-sanitization.test.d.ts +2 -0
- package/dist/core/llm/message-sanitization.test.d.ts.map +1 -0
- package/dist/core/llm/message-sanitization.test.js +181 -0
- package/dist/core/llm/message-sanitization.test.js.map +1 -0
- package/dist/core/llm/middleware.d.ts +3 -3
- package/dist/core/llm/middleware.js +3 -3
- package/dist/core/llm/openrouter-mapping.test.js +21 -3
- package/dist/core/llm/openrouter-mapping.test.js.map +1 -1
- package/dist/core/llm/openrouter.d.ts.map +1 -1
- package/dist/core/llm/openrouter.js +6 -1
- package/dist/core/llm/openrouter.js.map +1 -1
- package/dist/core/llm/provider.d.ts +13 -10
- package/dist/core/llm/provider.d.ts.map +1 -1
- package/dist/core/llm/provider.js.map +1 -1
- package/dist/core/llm/state.d.ts +10 -0
- package/dist/core/llm/state.d.ts.map +1 -1
- package/dist/core/llm/state.js +9 -0
- package/dist/core/llm/state.js.map +1 -1
- package/dist/core/plugins/plugin-builder.d.ts +2 -0
- package/dist/core/plugins/plugin-builder.d.ts.map +1 -1
- package/dist/core/plugins/plugin-builder.js.map +1 -1
- package/dist/core/sessions/session.d.ts.map +1 -1
- package/dist/core/sessions/session.js +12 -2
- package/dist/core/sessions/session.js.map +1 -1
- package/dist/core/sessions/state.d.ts.map +1 -1
- package/dist/core/sessions/state.js +43 -5
- package/dist/core/sessions/state.js.map +1 -1
- package/dist/plugins/context-compact/context-compact.integration.test.js +36 -20
- package/dist/plugins/context-compact/context-compact.integration.test.js.map +1 -1
- package/dist/plugins/context-compact/context-compactor.d.ts.map +1 -1
- package/dist/plugins/context-compact/context-compactor.js +54 -9
- package/dist/plugins/context-compact/context-compactor.js.map +1 -1
- package/dist/plugins/context-compact/context-compactor.test.js +127 -4
- package/dist/plugins/context-compact/context-compactor.test.js.map +1 -1
- package/dist/plugins/limits-guard/config.d.ts +2 -0
- package/dist/plugins/limits-guard/config.d.ts.map +1 -1
- package/dist/plugins/limits-guard/limit-guard.d.ts +1 -0
- package/dist/plugins/limits-guard/limit-guard.d.ts.map +1 -1
- package/dist/plugins/limits-guard/limit-guard.js +11 -1
- package/dist/plugins/limits-guard/limit-guard.js.map +1 -1
- package/dist/plugins/limits-guard/limit-guard.test.js +9 -0
- package/dist/plugins/limits-guard/limit-guard.test.js.map +1 -1
- package/dist/plugins/limits-guard/limits-guard.integration.test.js +216 -1
- package/dist/plugins/limits-guard/limits-guard.integration.test.js.map +1 -1
- package/dist/plugins/limits-guard/plugin.d.ts +3 -1
- package/dist/plugins/limits-guard/plugin.d.ts.map +1 -1
- package/dist/plugins/limits-guard/plugin.js +48 -2
- package/dist/plugins/limits-guard/plugin.js.map +1 -1
- package/dist/plugins/services/plugin.d.ts.map +1 -1
- package/dist/plugins/services/plugin.js +4 -1
- package/dist/plugins/services/plugin.js.map +1 -1
- package/dist/plugins/services/prompt.js +3 -3
- package/dist/plugins/services/prompt.js.map +1 -1
- package/dist/plugins/services/schema.d.ts +8 -0
- package/dist/plugins/services/schema.d.ts.map +1 -1
- package/dist/plugins/services/services.integration.test.js +112 -0
- package/dist/plugins/services/services.integration.test.js.map +1 -1
- package/dist/plugins/uploads/plugin.d.ts.map +1 -1
- package/dist/plugins/uploads/plugin.js +10 -1
- package/dist/plugins/uploads/plugin.js.map +1 -1
- package/dist/plugins/uploads/uploads.integration.test.js +74 -28
- package/dist/plugins/uploads/uploads.integration.test.js.map +1 -1
- package/dist/testing/test-harness.d.ts +8 -0
- package/dist/testing/test-harness.d.ts.map +1 -1
- package/dist/testing/test-harness.js +18 -0
- package/dist/testing/test-harness.js.map +1 -1
- package/dist/transport/http/middleware/error-handler.d.ts +1 -1
- package/package.json +2 -2
- package/src/core/agents/agent.ts +124 -36
- package/src/core/agents/config.ts +6 -0
- package/src/core/agents/state.ts +9 -0
- package/src/core/context/state.ts +86 -27
- package/src/core/error-recovery.integration.test.ts +343 -0
- package/src/core/llm/anthropic.test.ts +7 -4
- package/src/core/llm/anthropic.ts +6 -1
- package/src/core/llm/message-sanitization.test.ts +217 -0
- package/src/core/llm/message-sanitization.ts +70 -0
- package/src/core/llm/middleware.ts +3 -3
- package/src/core/llm/openrouter-mapping.test.ts +24 -3
- package/src/core/llm/openrouter.ts +7 -8
- package/src/core/llm/provider.ts +14 -10
- package/src/core/llm/state.ts +10 -0
- package/src/core/plugins/plugin-builder.ts +6 -2
- package/src/core/sessions/session.ts +13 -3
- package/src/core/sessions/state.ts +48 -9
- package/src/plugins/context-compact/context-compact.integration.test.ts +38 -21
- package/src/plugins/context-compact/context-compactor.test.ts +150 -4
- package/src/plugins/context-compact/context-compactor.ts +64 -10
- package/src/plugins/limits-guard/config.ts +2 -0
- package/src/plugins/limits-guard/limit-guard.test.ts +13 -0
- package/src/plugins/limits-guard/limit-guard.ts +13 -1
- package/src/plugins/limits-guard/limits-guard.integration.test.ts +246 -1
- package/src/plugins/limits-guard/plugin.ts +53 -3
- package/src/plugins/services/plugin.ts +4 -1
- package/src/plugins/services/prompt.ts +3 -3
- package/src/plugins/services/schema.ts +8 -0
- package/src/plugins/services/services.integration.test.ts +129 -0
- package/src/plugins/uploads/plugin.ts +11 -1
- package/src/plugins/uploads/uploads.integration.test.ts +82 -28
- package/src/testing/test-harness.ts +20 -0
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { describe, expect, it } from 'bun:test'
|
|
2
2
|
import z from 'zod/v4'
|
|
3
|
+
import { agentEvents } from '~/core/agents/state.js'
|
|
3
4
|
import { MockLLMProvider } from '~/core/llm/mock.js'
|
|
4
5
|
import { selectPluginState } from '~/core/sessions/reducer.js'
|
|
5
6
|
import { createTestPreset, TestHarness } from '~/testing/index.js'
|
|
7
|
+
import type { TestSession } from '~/testing/index.js'
|
|
6
8
|
import type { UploadsState } from './state.js'
|
|
7
9
|
import { uploadEvents } from './state.js'
|
|
8
10
|
|
|
@@ -17,6 +19,12 @@ function okValue<T>(result: { ok: boolean; value?: unknown }, schema: z.ZodType<
|
|
|
17
19
|
return schema.parse(result.value)
|
|
18
20
|
}
|
|
19
21
|
|
|
22
|
+
async function pauseEntryAgent(session: TestSession): Promise<void> {
|
|
23
|
+
const entryAgentId = session.getEntryAgentId()
|
|
24
|
+
if (!entryAgentId) throw new Error('Expected entry agent')
|
|
25
|
+
await session.pauseAgent(entryAgentId, 'Keep upload pending for storage test')
|
|
26
|
+
}
|
|
27
|
+
|
|
20
28
|
const uploadResultSchema = z.object({
|
|
21
29
|
uploadId: z.string(),
|
|
22
30
|
status: z.enum(['ready', 'failed']),
|
|
@@ -174,7 +182,7 @@ describe('uploads plugin', () => {
|
|
|
174
182
|
// =========================================================================
|
|
175
183
|
|
|
176
184
|
describe('dequeue', () => {
|
|
177
|
-
it('
|
|
185
|
+
it('upload arriving while agent is idle wakes exactly one inference', async () => {
|
|
178
186
|
const harness = new TestHarness({
|
|
179
187
|
presets: [createTestPreset()],
|
|
180
188
|
llmProvider: MockLLMProvider.withFixedResponse({ content: 'Got it', toolCalls: [] }),
|
|
@@ -191,8 +199,8 @@ describe('uploads plugin', () => {
|
|
|
191
199
|
fileBuffer: fileContent,
|
|
192
200
|
})
|
|
193
201
|
|
|
194
|
-
|
|
195
|
-
|
|
202
|
+
await session.waitForIdle()
|
|
203
|
+
expect(harness.llmProvider.getCallCount()).toBe(1)
|
|
196
204
|
|
|
197
205
|
// Check that the LLM received the attachment in its messages
|
|
198
206
|
const lastRequest = harness.llmProvider.getLastRequest()
|
|
@@ -209,41 +217,48 @@ describe('uploads plugin', () => {
|
|
|
209
217
|
expect(attachmentMessage.content).toContain('filename="doc.txt"')
|
|
210
218
|
expect(attachmentMessage.content).toContain('type="text/plain"')
|
|
211
219
|
|
|
220
|
+
const consumedEvents = await session.getEventsByType(agentEvents, 'agent_input_consumed')
|
|
221
|
+
expect(consumedEvents).toHaveLength(1)
|
|
222
|
+
expect(consumedEvents[0].sourcePlugins).toContain('uploads')
|
|
223
|
+
|
|
212
224
|
await harness.shutdown()
|
|
213
225
|
})
|
|
214
226
|
|
|
215
|
-
it('
|
|
227
|
+
it('several quick uploads debounce into one inference and one consumption event', async () => {
|
|
216
228
|
const harness = new TestHarness({
|
|
217
229
|
presets: [createTestPreset()],
|
|
218
230
|
llmProvider: MockLLMProvider.withFixedResponse({ content: 'Got it', toolCalls: [] }),
|
|
219
231
|
})
|
|
220
232
|
|
|
221
233
|
const session = await harness.createSession('test')
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
234
|
+
const filenames = ['one.txt', 'two.txt', 'three.txt']
|
|
235
|
+
await Promise.all(filenames.map((filename) => {
|
|
236
|
+
const fileContent = Buffer.from(`Content of ${filename}`)
|
|
237
|
+
return session.callPluginMethod('uploads.upload', {
|
|
238
|
+
sessionId: String(session.sessionId),
|
|
239
|
+
filename,
|
|
240
|
+
mimeType: 'text/plain',
|
|
241
|
+
size: fileContent.length,
|
|
242
|
+
fileBuffer: fileContent,
|
|
243
|
+
})
|
|
244
|
+
}))
|
|
231
245
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
expect(uploadsBefore).toBeDefined()
|
|
235
|
-
if (!uploadsBefore) throw new Error('Expected uploads state')
|
|
236
|
-
expect(uploadsBefore.pending).toHaveLength(1)
|
|
246
|
+
await session.waitForIdle()
|
|
247
|
+
expect(harness.llmProvider.getCallCount()).toBe(1)
|
|
237
248
|
|
|
238
|
-
|
|
239
|
-
|
|
249
|
+
const lastRequest = harness.llmProvider.getLastRequest()
|
|
250
|
+
expect(lastRequest).toBeDefined()
|
|
251
|
+
const requestText = lastRequest?.messages
|
|
252
|
+
.map((message) => typeof message.content === 'string' ? message.content : '')
|
|
253
|
+
.join('\n') ?? ''
|
|
254
|
+
for (const filename of filenames) {
|
|
255
|
+
expect(requestText).toContain(`filename="${filename}"`)
|
|
256
|
+
}
|
|
240
257
|
|
|
241
|
-
// After inference, should be consumed
|
|
242
258
|
const consumedEvents = await session.getEventsByType(uploadEvents, 'attachments_consumed')
|
|
243
259
|
expect(consumedEvents).toHaveLength(1)
|
|
244
|
-
expect(consumedEvents[0].uploadIds).toHaveLength(
|
|
260
|
+
expect(consumedEvents[0].uploadIds).toHaveLength(3)
|
|
245
261
|
|
|
246
|
-
// Pending should be empty
|
|
247
262
|
const uploadsAfter = selectPluginState<UploadsState>(session.state, 'uploads')
|
|
248
263
|
expect(uploadsAfter).toBeDefined()
|
|
249
264
|
if (!uploadsAfter) throw new Error('Expected uploads state')
|
|
@@ -251,6 +266,36 @@ describe('uploads plugin', () => {
|
|
|
251
266
|
|
|
252
267
|
await harness.shutdown()
|
|
253
268
|
})
|
|
269
|
+
|
|
270
|
+
it('upload does not wake a paused agent', async () => {
|
|
271
|
+
const harness = new TestHarness({
|
|
272
|
+
presets: [createTestPreset()],
|
|
273
|
+
llmProvider: MockLLMProvider.withFixedResponse({ content: 'Got it', toolCalls: [] }),
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
const session = await harness.createSession('test')
|
|
277
|
+
const entryAgentId = session.getEntryAgentId()
|
|
278
|
+
if (!entryAgentId) throw new Error('Expected entry agent')
|
|
279
|
+
await session.pauseAgent(entryAgentId, 'Paused by user')
|
|
280
|
+
|
|
281
|
+
const fileContent = Buffer.from('Wait until resume')
|
|
282
|
+
await session.callPluginMethod('uploads.upload', {
|
|
283
|
+
sessionId: String(session.sessionId),
|
|
284
|
+
filename: 'paused.txt',
|
|
285
|
+
mimeType: 'text/plain',
|
|
286
|
+
size: fileContent.length,
|
|
287
|
+
fileBuffer: fileContent,
|
|
288
|
+
})
|
|
289
|
+
|
|
290
|
+
await new Promise((resolve) => setTimeout(resolve, 25))
|
|
291
|
+
expect(harness.llmProvider.getCallCount()).toBe(0)
|
|
292
|
+
expect(session.state.agents.get(entryAgentId)?.status).toBe('paused')
|
|
293
|
+
|
|
294
|
+
const uploads = selectPluginState<UploadsState>(session.state, 'uploads')
|
|
295
|
+
expect(uploads?.pending).toHaveLength(1)
|
|
296
|
+
|
|
297
|
+
await harness.shutdown()
|
|
298
|
+
})
|
|
254
299
|
})
|
|
255
300
|
|
|
256
301
|
// =========================================================================
|
|
@@ -265,6 +310,7 @@ describe('uploads plugin', () => {
|
|
|
265
310
|
})
|
|
266
311
|
|
|
267
312
|
const session = await harness.createSession('test')
|
|
313
|
+
await pauseEntryAgent(session)
|
|
268
314
|
|
|
269
315
|
await session.callPluginMethod('uploads.upload', {
|
|
270
316
|
sessionId: String(session.sessionId),
|
|
@@ -304,6 +350,7 @@ describe('uploads plugin', () => {
|
|
|
304
350
|
})
|
|
305
351
|
|
|
306
352
|
const session = await harness.createSession('test')
|
|
353
|
+
await pauseEntryAgent(session)
|
|
307
354
|
|
|
308
355
|
const uploadData = okValue(
|
|
309
356
|
await session.callPluginMethod('uploads.upload', {
|
|
@@ -340,6 +387,7 @@ describe('uploads plugin', () => {
|
|
|
340
387
|
})
|
|
341
388
|
|
|
342
389
|
const session = await harness.createSession('test')
|
|
390
|
+
await pauseEntryAgent(session)
|
|
343
391
|
|
|
344
392
|
const uploadData = okValue(
|
|
345
393
|
await session.callPluginMethod('uploads.upload', {
|
|
@@ -381,6 +429,7 @@ describe('uploads plugin', () => {
|
|
|
381
429
|
})
|
|
382
430
|
|
|
383
431
|
const session = await harness.createSession('test')
|
|
432
|
+
await pauseEntryAgent(session)
|
|
384
433
|
|
|
385
434
|
const uploadData = okValue(
|
|
386
435
|
await session.callPluginMethod('uploads.upload', {
|
|
@@ -417,6 +466,7 @@ describe('uploads plugin', () => {
|
|
|
417
466
|
})
|
|
418
467
|
|
|
419
468
|
const session = await harness.createSession('test')
|
|
469
|
+
await pauseEntryAgent(session)
|
|
420
470
|
|
|
421
471
|
const uploadData = okValue(
|
|
422
472
|
await session.callPluginMethod('uploads.upload', {
|
|
@@ -482,6 +532,7 @@ describe('uploads plugin', () => {
|
|
|
482
532
|
})
|
|
483
533
|
|
|
484
534
|
const session = await harness.createSession('test')
|
|
535
|
+
await pauseEntryAgent(session)
|
|
485
536
|
|
|
486
537
|
const uploadData = okValue(
|
|
487
538
|
await session.callPluginMethod('uploads.upload', {
|
|
@@ -617,7 +668,7 @@ describe('uploads plugin', () => {
|
|
|
617
668
|
// uploadAsync method
|
|
618
669
|
// =========================================================================
|
|
619
670
|
|
|
620
|
-
it('uploadAsync returns processing
|
|
671
|
+
it('uploadAsync returns processing, then ready upload wakes the idle agent', async () => {
|
|
621
672
|
const harness = new TestHarness({
|
|
622
673
|
presets: [createTestPreset()],
|
|
623
674
|
llmProvider: MockLLMProvider.withFixedResponse({ content: 'Ok', toolCalls: [] }),
|
|
@@ -655,12 +706,13 @@ describe('uploads plugin', () => {
|
|
|
655
706
|
expect(own[0].status).toBe('processing')
|
|
656
707
|
expect(own[1].status).toBe('ready')
|
|
657
708
|
|
|
658
|
-
|
|
709
|
+
await session.waitForIdle()
|
|
710
|
+
expect(harness.llmProvider.getCallCount()).toBe(1)
|
|
711
|
+
|
|
712
|
+
// The ready upload was delivered and consumed by the woken agent.
|
|
659
713
|
const uploads = selectPluginState<UploadsState>(session.state, 'uploads')
|
|
660
714
|
if (!uploads) throw new Error('Expected uploads state')
|
|
661
|
-
expect(uploads.pending).toHaveLength(
|
|
662
|
-
expect(uploads.pending[0]?.uploadId).toBe(data.uploadId)
|
|
663
|
-
expect(uploads.pending[0]?.status).toBe('ready')
|
|
715
|
+
expect(uploads.pending).toHaveLength(0)
|
|
664
716
|
|
|
665
717
|
// First notification was processing.
|
|
666
718
|
const all = harness.notifications.getByType('uploads', 'uploadStatusChanged')
|
|
@@ -704,6 +756,7 @@ describe('uploads plugin', () => {
|
|
|
704
756
|
})
|
|
705
757
|
|
|
706
758
|
const session = await harness.createSession('test')
|
|
759
|
+
await pauseEntryAgent(session)
|
|
707
760
|
|
|
708
761
|
const result = await session.callPluginMethod('uploads.uploadAsync', {
|
|
709
762
|
sessionId: String(session.sessionId),
|
|
@@ -743,6 +796,7 @@ describe('uploads plugin', () => {
|
|
|
743
796
|
})
|
|
744
797
|
|
|
745
798
|
const session = await harness.createSession('test')
|
|
799
|
+
await pauseEntryAgent(session)
|
|
746
800
|
|
|
747
801
|
const uploadData = okValue(
|
|
748
802
|
await session.callPluginMethod('uploads.upload', {
|
|
@@ -275,6 +275,26 @@ export class TestSession {
|
|
|
275
275
|
return this.session.getEntryAgentId()
|
|
276
276
|
}
|
|
277
277
|
|
|
278
|
+
/**
|
|
279
|
+
* Pause an agent (the manual-pause API).
|
|
280
|
+
*/
|
|
281
|
+
async pauseAgent(agentId: AgentId, message?: string): Promise<void> {
|
|
282
|
+
const result = await this.session.pauseAgent(agentId, message)
|
|
283
|
+
if (!result.ok) {
|
|
284
|
+
throw new Error(`pauseAgent failed: ${result.error.type} — ${result.error.message}`)
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Resume a paused (or errored) agent.
|
|
290
|
+
*/
|
|
291
|
+
async resumeAgent(agentId: AgentId): Promise<void> {
|
|
292
|
+
const result = await this.session.resumeAgent(agentId)
|
|
293
|
+
if (!result.ok) {
|
|
294
|
+
throw new Error(`resumeAgent failed: ${result.error.type} — ${result.error.message}`)
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
278
298
|
/**
|
|
279
299
|
* Close the session.
|
|
280
300
|
*/
|