@dxos/agent-runtime 0.0.0 → 0.11.0
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/lib/chunk-AgentService.mjs +789 -0
- package/dist/lib/chunk-AgentService.mjs.map +1 -0
- package/dist/lib/index.mjs +2 -0
- package/dist/lib/testing.mjs +294 -0
- package/dist/lib/testing.mjs.map +1 -0
- package/dist/types/src/agent-service/AgentService.d.ts +4 -4
- package/dist/types/src/agent-service/AgentService.d.ts.map +1 -1
- package/dist/types/src/agent-service/agent-process.d.ts +173 -5
- package/dist/types/src/agent-service/agent-process.d.ts.map +1 -1
- package/dist/types/src/agent-service/delegation-scripted.test.d.ts +2 -0
- package/dist/types/src/agent-service/delegation-scripted.test.d.ts.map +1 -0
- package/dist/types/src/assistant-session-tests/scripted-loop.test.d.ts +2 -0
- package/dist/types/src/assistant-session-tests/scripted-loop.test.d.ts.map +1 -0
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/testing/assistant-test-layer.d.ts +6 -2
- package/dist/types/src/testing/assistant-test-layer.d.ts.map +1 -1
- package/dist/types/src/testing/index.d.ts +2 -0
- package/dist/types/src/testing/index.d.ts.map +1 -1
- package/dist/types/src/testing/test-agent.d.ts +70 -0
- package/dist/types/src/testing/test-agent.d.ts.map +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +17 -16
- package/src/agent-service/AgentService.conversations.json +1 -1
- package/src/agent-service/AgentService.test.ts +115 -58
- package/src/agent-service/AgentService.ts +44 -15
- package/src/agent-service/README.md +4 -4
- package/src/agent-service/agent-process.ts +31 -12
- package/src/agent-service/delegation-scripted.test.ts +123 -0
- package/src/assistant-session-tests/request.conversations.json +1 -1
- package/src/assistant-session-tests/request.test.ts +2 -2
- package/src/assistant-session-tests/scripted-loop.test.ts +119 -0
- package/src/assistant-session-tests/xml-response.conversations.json +1 -1
- package/src/assistant-session-tests/xml-response.test.ts +2 -2
- package/src/functions.conversations.json +1 -1
- package/src/functions.test.ts +2 -2
- package/src/index.ts +0 -2
- package/src/testing/assistant-test-layer.ts +32 -1
- package/src/testing/index.ts +3 -0
- package/src/testing/test-agent.ts +104 -0
- package/dist/lib/neutral/chunk-EZKG2ZGG.mjs +0 -754
- package/dist/lib/neutral/chunk-EZKG2ZGG.mjs.map +0 -7
- package/dist/lib/neutral/index.mjs +0 -9
- package/dist/lib/neutral/index.mjs.map +0 -7
- package/dist/lib/neutral/meta.json +0 -1
- package/dist/lib/neutral/testing/index.mjs +0 -285
- package/dist/lib/neutral/testing/index.mjs.map +0 -7
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { describe, it } from '@effect/vitest';
|
|
6
|
+
import * as Effect from 'effect/Effect';
|
|
7
|
+
import * as Exit from 'effect/Exit';
|
|
8
|
+
import * as Schema from 'effect/Schema';
|
|
9
|
+
import { expect } from 'vitest';
|
|
10
|
+
|
|
11
|
+
import { ScriptedLanguageModel } from '@dxos/ai/testing';
|
|
12
|
+
import { Operation, OperationHandlerSet } from '@dxos/compute';
|
|
13
|
+
import { ProcessManager } from '@dxos/compute-runtime';
|
|
14
|
+
import { TestHelpers } from '@dxos/effect/testing';
|
|
15
|
+
import { DXN, EntityId } from '@dxos/keys';
|
|
16
|
+
|
|
17
|
+
import { AssistantTestLayer } from '../testing';
|
|
18
|
+
import * as AgentService from './AgentService';
|
|
19
|
+
import { type DelegationStrategy } from './delegation-strategy';
|
|
20
|
+
|
|
21
|
+
const { text, scriptedAiService } = ScriptedLanguageModel;
|
|
22
|
+
|
|
23
|
+
EntityId.dangerouslyDisableRandomness();
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Trivial child operation the stub strategy spawns as a sub-agent. Returns a derived string
|
|
27
|
+
* synchronously so the delegation lifecycle is exercised without any model turn on the child.
|
|
28
|
+
*/
|
|
29
|
+
const DelegatedWork = Operation.make({
|
|
30
|
+
meta: {
|
|
31
|
+
key: DXN.make('org.dxos.function.delegatedWork'),
|
|
32
|
+
name: 'Delegated work',
|
|
33
|
+
description: 'Performs a delegated unit of work',
|
|
34
|
+
},
|
|
35
|
+
input: Schema.String,
|
|
36
|
+
output: Schema.String,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const handlers = OperationHandlerSet.make(
|
|
40
|
+
DelegatedWork.pipe(
|
|
41
|
+
Operation.withHandler(
|
|
42
|
+
Effect.fnUntraced(function* (input) {
|
|
43
|
+
return `done: ${input}`;
|
|
44
|
+
}),
|
|
45
|
+
),
|
|
46
|
+
),
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
interface DelegationHarness {
|
|
50
|
+
/** Work the stub strategy delegates on the next reconcile (keyed by a stable id). */
|
|
51
|
+
pending: { id: string; input: string }[];
|
|
52
|
+
/** Completions observed via the strategy's `onComplete` callback. */
|
|
53
|
+
completed: { id: string; exit: Exit.Exit<unknown> }[];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const delegationHarness: DelegationHarness = { pending: [], completed: [] };
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Stub {@link DelegationStrategy} driven by {@link delegationHarness}: each reconcile delegates the
|
|
60
|
+
* pending work not already in flight and records completions, so the test can assert the
|
|
61
|
+
* reconcile → spawn → onChildEvent → onComplete loop in isolation from any plan/chat semantics
|
|
62
|
+
* (the real strategy is covered by `assistant-toolkit/src/supervisor/delegation-strategy.test.ts`).
|
|
63
|
+
*/
|
|
64
|
+
const StubDelegationStrategy: DelegationStrategy = {
|
|
65
|
+
reconcile: (_feed, activeIds) =>
|
|
66
|
+
Effect.succeed(
|
|
67
|
+
delegationHarness.pending
|
|
68
|
+
.filter((work) => !activeIds.has(work.id))
|
|
69
|
+
.map((work) => ({
|
|
70
|
+
id: work.id,
|
|
71
|
+
spawn: Effect.gen(function* () {
|
|
72
|
+
const invoker = yield* ProcessManager.ProcessOperationInvoker.Service;
|
|
73
|
+
const fiber = yield* invoker.invokeFiber(DelegatedWork, work.input);
|
|
74
|
+
return fiber.pid;
|
|
75
|
+
}),
|
|
76
|
+
})),
|
|
77
|
+
),
|
|
78
|
+
onComplete: (_feed, id, exit) =>
|
|
79
|
+
Effect.sync(() => {
|
|
80
|
+
delegationHarness.completed.push({ id, exit });
|
|
81
|
+
delegationHarness.pending = delegationHarness.pending.filter((work) => work.id !== id);
|
|
82
|
+
}),
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// The stub strategy delegates regardless of the model output, so a single text turn is all the
|
|
86
|
+
// supervisor needs; the child operation runs no model at all.
|
|
87
|
+
const TestLayer = AssistantTestLayer({
|
|
88
|
+
agent: { delegationStrategy: StubDelegationStrategy },
|
|
89
|
+
aiService: scriptedAiService([{ parts: [text('Working on it.')] }]),
|
|
90
|
+
operationHandlers: [handlers],
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
describe('AgentProcess delegation lifecycle (scripted)', () => {
|
|
94
|
+
it.scoped(
|
|
95
|
+
'delegates work to a sub-agent and folds the result back on completion',
|
|
96
|
+
Effect.fnUntraced(
|
|
97
|
+
function* (_) {
|
|
98
|
+
delegationHarness.pending = [{ id: 'task-1', input: 'forty-two' }];
|
|
99
|
+
delegationHarness.completed = [];
|
|
100
|
+
|
|
101
|
+
const session = yield* AgentService.createSession();
|
|
102
|
+
yield* session.submitPrompt('Please do the pending work.');
|
|
103
|
+
// Settles on the turn's reply; the delegated child runs in the background (not awaited here).
|
|
104
|
+
yield* session.waitForCompletion();
|
|
105
|
+
|
|
106
|
+
// The post-turn reconcile spawned a linked child; its exit drives onChildEvent → onComplete.
|
|
107
|
+
yield* Effect.promise(async () => {
|
|
108
|
+
await expect.poll(() => delegationHarness.completed.length, { timeout: 5_000 }).toBe(1);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const [completion] = delegationHarness.completed;
|
|
112
|
+
expect(completion.id).toBe('task-1');
|
|
113
|
+
expect(Exit.isSuccess(completion.exit)).toBe(true);
|
|
114
|
+
if (Exit.isSuccess(completion.exit)) {
|
|
115
|
+
expect(completion.exit.value).toBe('done: forty-two');
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
Effect.provide(TestLayer),
|
|
119
|
+
TestHelpers.provideTestContext,
|
|
120
|
+
),
|
|
121
|
+
{ timeout: 15_000 },
|
|
122
|
+
);
|
|
123
|
+
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"conversations":[{"parameters":{"model":"dxn:com.anthropic.model.claude-opus-4-8.default","stream":true,"tools":[]},"prompt":{"content":[{"role":"user","content":"Hello world!","options":{"anthropic":{"cacheControl":{"ttl":"5m","type":"ephemeral"}}}}]},"response":[{"type":"response-metadata","id":"msg_01FpufqYyxESFAyhqbgxxDzh","modelId":"claude-opus-4-8","timestamp":"1970-01-01T00:00:00.000Z","metadata":{}},{"type":"text-start","id":"0","metadata":{}},{"type":"text-delta","id":"0","delta":"Hello!","metadata":{}},{"type":"text-delta","id":"0","delta":" 👋 Nice to meet you!\n\nHow can I help you today? Whether you have a question, need help with a task, want to brainstorm ide","metadata":{}},{"type":"text-delta","id":"0","delta":"as, or just feel like chatting, I'm happy to assist.","metadata":{}},{"type":"text-end","id":"0","metadata":{}},{"type":"finish","reason":"stop","usage":{"inputTokens":11,"outputTokens":69,"totalTokens":80},"metadata":{"anthropic":{"usage":{"cache_creation":{"ephemeral_1h_input_tokens":0,"ephemeral_5m_input_tokens":0},"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"input_tokens":11,"output_tokens":4,"server_tool_use":null,"service_tier":"standard"}}}}]},{"parameters":{"model":"dxn:com.anthropic.model.claude-opus-4-8.default","stream":true,"tools":[{"name":"Calculator","description":"Basic calculator tool","inputSchema":{"type":"object","required":["input"],"properties":{"input":{"type":"string","description":"The calculation to perform."}},"additionalProperties":false}}]},"prompt":{"content":[{"role":"user","content":"What is 10 + 30?","options":{"anthropic":{"cacheControl":{"ttl":"5m","type":"ephemeral"}}}}]},"response":[{"type":"response-metadata","id":"msg_01ArHuDbCcJbCuEbKQWbVgNP","modelId":"claude-opus-4-8","timestamp":"1970-01-01T00:00:00.000Z","metadata":{}},{"type":"reasoning-start","id":"0","metadata":{}},{"type":"reasoning-delta","id":"0","delta":"","metadata":{"anthropic":{"type":"thinking","signature":"EswBCmMIDxgCKkCYWMo4h+gTuoOJyej3UAvI/z9N7dYZpIw1rpyM0pAj/LGQIcM+c4h7Vk4GHtKp709n07ZxKqSK/CNlabX+vVcgMg9jbGF1ZGUtb3B1cy00LTg4AEIIdGhpbmtpbmcSDOr1/rlK3KARyw42YRoMbyke63kbVOmiO98bIjCpiMfmStMK3sXtw2tSO6NVvn9livrg8FdZfGGPbiuI93daDYpxAQ5tPsK/AIJ19V0qF9HjBzmlU4khZ3Xs+4azncZok1GLHI9IGAE="}}},{"type":"reasoning-end","id":"0","metadata":{}},{"type":"tool-params-start","id":"toolu_01Cwmh4VVGrpoEgGSvnC9mkB","name":"Calculator","providerExecuted":false,"metadata":{}},{"type":"tool-params-delta","id":"toolu_01Cwmh4VVGrpoEgGSvnC9mkB","delta":"","metadata":{}},{"type":"tool-params-delta","id":"toolu_01Cwmh4VVGrpoEgGSvnC9mkB","delta":"{\"inp","metadata":{}},{"type":"tool-params-delta","id":"toolu_01Cwmh4VVGrpoEgGSvnC9mkB","delta":"ut\": \"10 + 3","metadata":{}},{"type":"tool-params-delta","id":"toolu_01Cwmh4VVGrpoEgGSvnC9mkB","delta":"0\"}","metadata":{}},{"type":"tool-params-end","id":"toolu_01Cwmh4VVGrpoEgGSvnC9mkB","metadata":{}},{"type":"finish","reason":"tool-calls","usage":{"inputTokens":398,"outputTokens":59,"totalTokens":457},"metadata":{"anthropic":{"usage":{"cache_creation":{"ephemeral_1h_input_tokens":0,"ephemeral_5m_input_tokens":0},"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"input_tokens":398,"output_tokens":5,"server_tool_use":null,"service_tier":"standard"}}}}]},{"parameters":{"model":"dxn:com.anthropic.model.claude-opus-4-8.default","stream":true,"tools":[{"name":"Calculator","description":"Basic calculator tool","inputSchema":{"type":"object","required":["input"],"properties":{"input":{"type":"string","description":"The calculation to perform."}},"additionalProperties":false}}]},"prompt":{"content":[{"role":"user","content":"What is 10 + 30?","options":{}},{"role":"assistant","content":[{"type":"reasoning","text":"","options":{}},{"type":"tool-call","id":"toolu_01Cwmh4VVGrpoEgGSvnC9mkB","name":"Calculator","params":{"input":"10 + 30"},"providerExecuted":false,"options":{}}],"options":{}},{"role":"tool","content":[{"type":"tool-result","id":"toolu_01Cwmh4VVGrpoEgGSvnC9mkB","name":"Calculator","isFailure":false,"result":{"result":40},"providerExecuted":false,"options":{}}],"options":{"anthropic":{"cacheControl":{"ttl":"5m","type":"ephemeral"}}}}]},"response":[{"type":"response-metadata","id":"msg_014jK4oKvYXUZfkQH6rdg3Sk","modelId":"claude-opus-4-8","timestamp":"1970-01-01T00:00:00.000Z","metadata":{}},{"type":"text-start","id":"0","metadata":{}},{"type":"text-delta","id":"0","delta":"10","metadata":{}},{"type":"text-delta","id":"0","delta":" + 30 = **40**","metadata":{}},{"type":"text-end","id":"0","metadata":{}},{"type":"finish","reason":"stop","usage":{"inputTokens":461,"outputTokens":12,"totalTokens":473},"metadata":{"anthropic":{"usage":{"cache_creation":{"ephemeral_1h_input_tokens":0,"ephemeral_5m_input_tokens":0},"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"input_tokens":461,"output_tokens":1,"server_tool_use":null,"service_tier":"standard"}}}}]},{"parameters":{"model":"dxn:com.anthropic.model.claude-opus-4-8.default","stream":true,"tools":[{"name":"Calculator","description":"Basic calculator tool","inputSchema":{"type":"object","required":["input"],"properties":{"input":{"type":"string","description":"The calculation to perform."}},"additionalProperties":false}}]},"prompt":{"content":[{"role":"user","content":"I am testing error handling in tool paramter parsing. I want you to call the calculator tool but omit the input parameter to intentionally differ from the tool schema.","options":{"anthropic":{"cacheControl":{"ttl":"5m","type":"ephemeral"}}}}]},"response":[{"type":"response-metadata","id":"msg_013GLZgtSMQ6Xhy7dyj24QoQ","modelId":"claude-opus-4-8","timestamp":"1970-01-01T00:00:00.000Z","metadata":{}},{"type":"reasoning-start","id":"0","metadata":{}},{"type":"reasoning-delta","id":"0","delta":"","metadata":{"anthropic":{"type":"thinking","signature":"EsECCmMIDxgCKkBByEwRYF9GiBoM479XPHHs7dxOSQ3NPMauc8wLYse91ljriLub5fGeBmkPlt4rko3tqY+sTzS948f0a9sYSGwuMg9jbGF1ZGUtb3B1cy00LTg4AEIIdGhpbmtpbmcSDDb6sRNiZTblNxhf5RoMgNqpUCJZhZupbHGEIjA+7zxzzkHLPZzJd8jdl2u2RKWjbFh42dh92TCW8ZOQEpNYzHYhjVdG6UDAmyDTyeEqiwFnzQN2DeuuUOsZNrsAs8fpmOx/lqHGf/yalvrZBAtK7Q9ZTf5vUX1G7WuEWopM6aoRUK8VizJr9GP2RK2XFG3BG3RNr3cllZYsPImcKbGASX87CpPchuw5mnEFSyLXjhGclj7uKYBaXjQuYTqwmgzgfToQ41N4FnoiEUa2vaDXqVeNagVxM46U3lh8GAE="}}},{"type":"reasoning-end","id":"0","metadata":{}},{"type":"text-start","id":"1","metadata":{}},{"type":"text-delta","id":"1","delta":"I'll help you test the error handling.","metadata":{}},{"type":"text-delta","id":"1","delta":" Let me call the Calculator tool while intentionally omitting the required `input` parameter:","metadata":{}},{"type":"text-end","id":"1","metadata":{}},{"type":"tool-params-start","id":"toolu_01FKjZtYwnbyrMzyWH6fgK4k","name":"Calculator","providerExecuted":false,"metadata":{}},{"type":"tool-params-delta","id":"toolu_01FKjZtYwnbyrMzyWH6fgK4k","delta":"","metadata":{}},{"type":"tool-params-end","id":"toolu_01FKjZtYwnbyrMzyWH6fgK4k","metadata":{}}]},{"parameters":{"model":"dxn:com.anthropic.model.claude-opus-4-8.default","stream":true,"tools":[{"name":"Calculator","description":"Basic calculator tool","inputSchema":{"type":"object","required":["input"],"properties":{"input":{"type":"string","description":"The calculation to perform."}},"additionalProperties":false}}]},"prompt":{"content":[{"role":"user","content":"I am testing error handling in tool paramter parsing. I want you to call the calculator tool but omit the input parameter to intentionally differ from the tool schema.","options":{}},{"role":"assistant","content":[{"type":"reasoning","text":"","options":{}},{"type":"text","text":"I'll help you test the error handling. Let me call the Calculator tool while intentionally omitting the required `input` parameter:","options":{}},{"type":"tool-call","id":"toolu_01FKjZtYwnbyrMzyWH6fgK4k","name":"Calculator","params":{},"providerExecuted":false,"options":{}}],"options":{}},{"role":"tool","content":[{"type":"tool-result","id":"toolu_01FKjZtYwnbyrMzyWH6fgK4k","name":"Calculator","isFailure":true,"result":"MalformedOutput: { \"module\": \"Toolkit\", \"method\": \"Calculator.handle\", \"description\": \"Failed to decode tool call parameters for tool 'Calculator' from:\\n'{}'\", \"cause\": { readonly input: string }\n└─ [\"input\"]\n └─ is missing }\ncaused by:\nParseError: { readonly input: string }\n└─ [\"input\"]\n └─ is missing","providerExecuted":false,"options":{}}],"options":{"anthropic":{"cacheControl":{"ttl":"5m","type":"ephemeral"}}}}]},"response":[{"type":"response-metadata","id":"msg_01HjxuVYH2y4aPHMh5KcRc2E","modelId":"claude-opus-4-8","timestamp":"1970-01-01T00:00:00.000Z","metadata":{}},{"type":"text-start","id":"0","metadata":{}},{"type":"text-delta","id":"0","delta":"The","metadata":{}},{"type":"text-delta","id":"0","delta":" error handling worked as expected. Here's what happened when I called the Calculator tool without the required `input` parameter:\n\n**Error Type:** `MalformedOut","metadata":{}},{"type":"text-delta","id":"0","delta":"put` / `ParseError`\n\n**Key details from the error:**\n- **Module:** `Toolkit`\n- **Method:** `Calculator.hand","metadata":{}},{"type":"text-delta","id":"0","delta":"le`\n- **Description:** Failed to decode tool call parameters for tool 'Calculator' from the empty object `{}`\n- **Root cause:** The schema exp","metadata":{}},{"type":"text-delta","id":"0","delta":"ects `{ readonly input: string }`, but the `[\"input\"]` field **is missing**\n\nThe parser correctly:\n1. Det","metadata":{}},{"type":"text-delta","id":"0","delta":"ected that the `input` parameter was absent from the tool call\n2. Identified exactly which field was missing (`[\"input\"]`)\n3.","metadata":{}},{"type":"text-delta","id":"0","delta":" Reported the expected schema type (`{ readonly input: string }`)\n4. Returned a descriptive error rather than cr","metadata":{}},{"type":"text-delta","id":"0","delta":"ashing or passing through invalid data\n\nThis confirms your parameter validation is functioning properly—it r","metadata":{}},{"type":"text-delta","id":"0","delta":"ejects malformed tool calls that don't conform to the schema and provides a clear, traceable error message pointing to the specific missing field. Would you like to test any other error","metadata":{}},{"type":"text-delta","id":"0","delta":" cases (e.g., wrong type for `input`, or extra unexpected parameters)?","metadata":{}},{"type":"text-end","id":"0","metadata":{}},{"type":"finish","reason":"stop","usage":{"inputTokens":676,"outputTokens":381,"totalTokens":1057},"metadata":{"anthropic":{"usage":{"cache_creation":{"ephemeral_1h_input_tokens":0,"ephemeral_5m_input_tokens":0},"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"input_tokens":676,"output_tokens":1,"server_tool_use":null,"service_tier":"standard"}}}}]},{"parameters":{"model":"dxn:com.anthropic.model.claude-opus-4-8.default","stream":false,"tools":[]},"prompt":{"content":[{"role":"system","content":"<instructions>\n You are an agent that summarizes conversations.\n You will be given a conversation and you will need to summarize it to compact context.\n Your summary will be used by AI model to continue the conversation without seeing the original messages.\n Remove unnecessary details and focus on the most important information.\n Keep user requests, method of action, and progress.\n Keep the important objects and references in original format.\n It is very important you maintain unchanged ECHO DXNs and IDs of the objects.\n Output only the summary.\n</instructions>\n\n<example_output>\n Processing a flight booking confirmation email. The user requested a round-trip flight from Warsaw to Barcelona for March 15–22, 2026.\n\n Action taken:\n - Found existing booking object `echo:/01KHZWKQ9HPJYG147HV7CP5ARX` (type `echo:/01KHXAMH4F3V94RCYC7P7M2H0C` / `com.example.type.flight-booking`) matching confirmation code ABC123.\n - Updated the booking status from \"pending\" to \"confirmed\".\n - Added flight details: Outbound WAW→BCN Mar 15 08:30, Return BCN→WAW Mar 22 14:15, carrier LOT Polish Airlines.\n - Stored passenger info and seat assignments in notes.\n\n Booking details preserved: Confirmation ABC123, total 1,240 PLN, booked 2026-02-18, airline LOT Polish Airlines, flight numbers LO391/LO392.\n\n Status enum mapping for the booking schema: pending=Awaiting payment, confirmed=Booked, cancelled=Cancelled, completed=Flown.\n</example_output>\n\nTASK: Reply with the summary of the prior conversation.","options":{}},{"role":"user","content":"How many apples are in the basket?","options":{}},{"role":"assistant","content":"There are 10 apples in the basket.","options":{}},{"role":"user","content":"<instructions>\n You are an agent that summarizes conversations.\n You will be given a conversation and you will need to summarize it to compact context.\n Your summary will be used by AI model to continue the conversation without seeing the original messages.\n Remove unnecessary details and focus on the most important information.\n Keep user requests, method of action, and progress.\n Keep the important objects and references in original format.\n It is very important you maintain unchanged ECHO DXNs and IDs of the objects.\n Output only the summary.\n</instructions>\n\n<example_output>\n Processing a flight booking confirmation email. The user requested a round-trip flight from Warsaw to Barcelona for March 15–22, 2026.\n\n Action taken:\n - Found existing booking object `echo:/01KHZWKQ9HPJYG147HV7CP5ARX` (type `echo:/01KHXAMH4F3V94RCYC7P7M2H0C` / `com.example.type.flight-booking`) matching confirmation code ABC123.\n - Updated the booking status from \"pending\" to \"confirmed\".\n - Added flight details: Outbound WAW→BCN Mar 15 08:30, Return BCN→WAW Mar 22 14:15, carrier LOT Polish Airlines.\n - Stored passenger info and seat assignments in notes.\n\n Booking details preserved: Confirmation ABC123, total 1,240 PLN, booked 2026-02-18, airline LOT Polish Airlines, flight numbers LO391/LO392.\n\n Status enum mapping for the booking schema: pending=Awaiting payment, confirmed=Booked, cancelled=Cancelled, completed=Flown.\n</example_output>\n\nTASK: Reply with the summary of the prior conversation.","options":{}}]},"response":[{"type":"response-metadata","id":"msg_01HCQLhHL7h9m5bCqHbxMt3M","modelId":"claude-opus-4-8","timestamp":"1970-01-01T00:00:00.000Z","metadata":{}},{"type":"text","text":"The user asked how many apples are in the basket. I answered that there are 10 apples in the basket.","metadata":{}},{"type":"finish","reason":"stop","usage":{"inputTokens":1203,"outputTokens":32,"totalTokens":1235,"cachedInputTokens":0},"metadata":{"anthropic":{"usage":{"cache_creation":{"ephemeral_1h_input_tokens":0,"ephemeral_5m_input_tokens":0},"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"input_tokens":1203,"output_tokens":32,"server_tool_use":null,"service_tier":"standard"}}}}]},{"parameters":{"model":"dxn:com.anthropic.model.claude-opus-4-8.default","stream":true,"tools":[]},"prompt":{"content":[{"role":"assistant","content":"This is the continuation of a conversation that was compacted to preserve context-window space. Summary of what happened in this conversation previously: <summary>The user asked how many apples are in the basket. I answered that there are 10 apples in the basket.</summary>","options":{}},{"role":"user","content":"What did we talk about?","options":{"anthropic":{"cacheControl":{"ttl":"5m","type":"ephemeral"}}}}]},"response":[{"type":"response-metadata","id":"msg_01Xf6mR2pWbx3WLv6kwbLQiS","modelId":"claude-opus-4-8","timestamp":"1970-01-01T00:00:00.000Z","metadata":{}},{"type":"reasoning-start","id":"0","metadata":{}},{"type":"reasoning-delta","id":"0","delta":"","metadata":{"anthropic":{"type":"thinking","signature":"Ev0CCmMIDxgCKkBWs7g6G04DqgFvDZZ+e48xE0cc3EYv160jAgqxm5x2mLho7S+B8QaCfaBDfhLvDsYx3Bnxeig672zRkNSx4/zqMg9jbGF1ZGUtb3B1cy00LTg4AEIIdGhpbmtpbmcSDBlkR5QuxfQfXtLzfxoMSdaRl5SRKqYT3fm9IjC6COGxxqy0hBEoOowzEm/CyFwmPHgVTFuJxy866+hi5qfXTtmoF3nolcIGXsW9iJEqxwG9ks2sto1fbfLCcP+n+4RTylUusnjtMYYZ84cBeOxvtgPaiw86zfrrK2XR/+jJ4QcP6ugRIe+fxiaL1HNIT2fA0dvN2+FyKMw8ADm+rMZDE4q+6dUmMa2FQOVRYlnmW+XA7VPm9ogmsaRRNm8EemVKITV5N9P4Fbm/k+OqHGnTgc6kjWcRBk8O3jBQBVwEIRX4qtlxyIhUlfh/rP47hFGfu+OgOWtiOob8Bb8AU71ZFVvw+qW2+RGRq+dAMxbaWDAxW52/WoUzGAE="}}},{"type":"reasoning-end","id":"0","metadata":{}},{"type":"text-start","id":"1","metadata":{}},{"type":"text-delta","id":"1","delta":"Based on our earlier conversation, we talked about how many apples are in the basket","metadata":{}},{"type":"text-delta","id":"1","delta":". You asked me that question, and I told you there are 10 apples in the basket.\n\nIs there anything else you'd like to know, or would you like to continue from","metadata":{}},{"type":"text-delta","id":"1","delta":" there?","metadata":{}},{"type":"text-end","id":"1","metadata":{}},{"type":"finish","reason":"stop","usage":{"inputTokens":114,"outputTokens":126,"totalTokens":240},"metadata":{"anthropic":{"usage":{"cache_creation":{"ephemeral_1h_input_tokens":0,"ephemeral_5m_input_tokens":0},"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"input_tokens":114,"output_tokens":2,"server_tool_use":null,"service_tier":"standard"}}}}]}]}
|
|
1
|
+
{"conversations":[{"parameters":{"model":"dxn:com.anthropic.model.claude-opus-4-8.default","stream":true,"tools":[]},"prompt":{"content":[{"role":"user","content":"Hello world!","options":{"anthropic":{"cacheControl":{"ttl":"5m","type":"ephemeral"}}}}]},"response":[{"type":"response-metadata","id":"msg_011CddXi6boaghZ8K1b7uy7r","modelId":"claude-opus-4-8","timestamp":"1970-01-01T00:00:00.000Z","metadata":{}},{"type":"text-start","id":"0","metadata":{}},{"type":"text-delta","id":"0","delta":"Hello!","metadata":{}},{"type":"text-delta","id":"0","delta":" 👋 Nice to meet you.\n\nHow can I help you today? Whether you have a question, need help with a task, want","metadata":{}},{"type":"text-delta","id":"0","delta":" to brainstorm ideas, or just feel like chatting, I'm here for it. What's on your mind?","metadata":{}},{"type":"text-end","id":"0","metadata":{}},{"type":"finish","reason":"stop","usage":{"inputTokens":11,"outputTokens":74,"totalTokens":85},"metadata":{"anthropic":{"usage":{"cache_creation":{"ephemeral_1h_input_tokens":0,"ephemeral_5m_input_tokens":0},"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"input_tokens":11,"output_tokens":4,"server_tool_use":null,"service_tier":"standard"}}}}]},{"parameters":{"model":"dxn:com.anthropic.model.claude-opus-4-8.default","stream":true,"tools":[{"name":"Calculator","description":"Basic calculator tool","inputSchema":{"type":"object","required":["input"],"properties":{"input":{"type":"string","description":"The calculation to perform."}},"additionalProperties":false}}]},"prompt":{"content":[{"role":"user","content":"What is 10 + 30?","options":{"anthropic":{"cacheControl":{"ttl":"5m","type":"ephemeral"}}}}]},"response":[{"type":"response-metadata","id":"msg_011CddXiGqwtXurd24AmSyAk","modelId":"claude-opus-4-8","timestamp":"1970-01-01T00:00:00.000Z","metadata":{}},{"type":"reasoning-start","id":"0","metadata":{}},{"type":"reasoning-delta","id":"0","delta":"","metadata":{"anthropic":{"type":"thinking","signature":"Ev8BCokBCBAYAipAaAFgmMi/qRcaxRrbn15Wr7FHW/Ys5Mjfl1PcWewHmQo8UAMyMh0TT+m5j9lGbGtZfJtVIONKHaHttygmB5vj/TIPY2xhdWRlLW9wdXMtNC04OABCCHRoaW5raW5nWiRlOTk3NGY0Mi0xMmMzLTRlYWUtOTA5OS01OGNiNGE1MzQyY2YSDPmeZXwPQCsn6TZ+RhoMMx3RKUQTva/v2nQ/IjD1Dr/+Bt7QVTh0+2DlBJKy3V6UvjprvLFE1yWm+raMSMPRkJs2xgG4m+mSWJkfgEAqI4ZcCPLzlt5v+Ac2+FndfQlEdTt3UdFflXu2V5yc+RelOf2yGAE="}}},{"type":"reasoning-end","id":"0","metadata":{}},{"type":"tool-params-start","id":"toolu_01UEPKtzrMydpiwYNfL1SNq5","name":"Calculator","providerExecuted":false,"metadata":{}},{"type":"tool-params-delta","id":"toolu_01UEPKtzrMydpiwYNfL1SNq5","delta":"","metadata":{}},{"type":"tool-params-delta","id":"toolu_01UEPKtzrMydpiwYNfL1SNq5","delta":"{\"","metadata":{}},{"type":"tool-params-delta","id":"toolu_01UEPKtzrMydpiwYNfL1SNq5","delta":"input\": \"","metadata":{}},{"type":"tool-params-delta","id":"toolu_01UEPKtzrMydpiwYNfL1SNq5","delta":"10 + 30\"}","metadata":{}},{"type":"tool-params-end","id":"toolu_01UEPKtzrMydpiwYNfL1SNq5","metadata":{}},{"type":"finish","reason":"tool-calls","usage":{"inputTokens":398,"outputTokens":62,"totalTokens":460},"metadata":{"anthropic":{"usage":{"cache_creation":{"ephemeral_1h_input_tokens":0,"ephemeral_5m_input_tokens":0},"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"input_tokens":398,"output_tokens":5,"server_tool_use":null,"service_tier":"standard"}}}}]},{"parameters":{"model":"dxn:com.anthropic.model.claude-opus-4-8.default","stream":true,"tools":[{"name":"Calculator","description":"Basic calculator tool","inputSchema":{"type":"object","required":["input"],"properties":{"input":{"type":"string","description":"The calculation to perform."}},"additionalProperties":false}}]},"prompt":{"content":[{"role":"user","content":"What is 10 + 30?","options":{}},{"role":"assistant","content":[{"type":"reasoning","text":"","options":{}},{"type":"tool-call","id":"toolu_01UEPKtzrMydpiwYNfL1SNq5","name":"Calculator","params":{"input":"10 + 30"},"providerExecuted":false,"options":{}}],"options":{}},{"role":"tool","content":[{"type":"tool-result","id":"toolu_01UEPKtzrMydpiwYNfL1SNq5","name":"Calculator","isFailure":false,"result":{"result":40},"providerExecuted":false,"options":{}}],"options":{"anthropic":{"cacheControl":{"ttl":"5m","type":"ephemeral"}}}}]},"response":[{"type":"response-metadata","id":"msg_011CddXiQKuTNofVZNv7ftKC","modelId":"claude-opus-4-8","timestamp":"1970-01-01T00:00:00.000Z","metadata":{}},{"type":"text-start","id":"0","metadata":{}},{"type":"text-delta","id":"0","delta":"10 + 30 = **","metadata":{}},{"type":"text-delta","id":"0","delta":"40**","metadata":{}},{"type":"text-end","id":"0","metadata":{}},{"type":"finish","reason":"stop","usage":{"inputTokens":461,"outputTokens":12,"totalTokens":473},"metadata":{"anthropic":{"usage":{"cache_creation":{"ephemeral_1h_input_tokens":0,"ephemeral_5m_input_tokens":0},"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"input_tokens":461,"output_tokens":8,"server_tool_use":null,"service_tier":"standard"}}}}]},{"parameters":{"model":"dxn:com.anthropic.model.claude-opus-4-8.default","stream":true,"tools":[{"name":"Calculator","description":"Basic calculator tool","inputSchema":{"type":"object","required":["input"],"properties":{"input":{"type":"string","description":"The calculation to perform."}},"additionalProperties":false}}]},"prompt":{"content":[{"role":"user","content":"I am testing error handling in tool paramter parsing. I want you to call the calculator tool but omit the input parameter to intentionally differ from the tool schema.","options":{"anthropic":{"cacheControl":{"ttl":"5m","type":"ephemeral"}}}}]},"response":[{"type":"response-metadata","id":"msg_011CddXiVuWaX3WxBf7g7Fap","modelId":"claude-opus-4-8","timestamp":"1970-01-01T00:00:00.000Z","metadata":{}},{"type":"reasoning-start","id":"0","metadata":{}},{"type":"reasoning-delta","id":"0","delta":"","metadata":{"anthropic":{"type":"thinking","signature":"Ep0DCokBCBAYAipAeeuUhKfAyCosekSas8ELYdT95l+VVoBpnvsx6243g/3Xkps6k2xZoBBYvdE6YCoudyclkiePtAczj9w0DfYnhTIPY2xhdWRlLW9wdXMtNC04OABCCHRoaW5raW5nWiRlOTk3NGY0Mi0xMmMzLTRlYWUtOTA5OS01OGNiNGE1MzQyY2YSDDW49tkYXAmFUeq3MxoMxQjA7SOaQ1yf43OBIjBCaOso1/IehkUOgRtzcHqp4cxI+1QWv/w1U8nqxA3Z76dzbdJfctCtJ2RX0c5lbFYqwAEQR/uMzFROk4Kq66JHUc9nAkK2bFwjTVoG3gzczhw/cj7cg4TAfHhzoupEvq7Fc/W37scxNulYN6PFm26ugdc1lXdG5DV+uAWXquYUHA5jyiQDCSBa0rtC8p+VEIMPFgMXiCPgaqKVo8rOYCNJs4g4QCsDOI1Lj6N7+/99EumksxWHRSUe+NMETqdKKvQxFl/4SwS+raQLtTpvyKkCSZXiGYiyaWAli9MIQqjaOhl2In0QWbASFJM1jHrqkhDTJfAYAQ=="}}},{"type":"reasoning-end","id":"0","metadata":{}},{"type":"text-start","id":"1","metadata":{}},{"type":"text-delta","id":"1","delta":"I'll help you test the error handling. I'll call the calculator t","metadata":{}},{"type":"text-delta","id":"1","delta":"ool while intentionally omitting the required `input` parameter.","metadata":{}},{"type":"text-end","id":"1","metadata":{}},{"type":"tool-params-start","id":"toolu_01JstTa1BrCcddYHJXFH4pWB","name":"Calculator","providerExecuted":false,"metadata":{}},{"type":"tool-params-delta","id":"toolu_01JstTa1BrCcddYHJXFH4pWB","delta":"","metadata":{}},{"type":"tool-params-end","id":"toolu_01JstTa1BrCcddYHJXFH4pWB","metadata":{}}]},{"parameters":{"model":"dxn:com.anthropic.model.claude-opus-4-8.default","stream":true,"tools":[{"name":"Calculator","description":"Basic calculator tool","inputSchema":{"type":"object","required":["input"],"properties":{"input":{"type":"string","description":"The calculation to perform."}},"additionalProperties":false}}]},"prompt":{"content":[{"role":"user","content":"I am testing error handling in tool paramter parsing. I want you to call the calculator tool but omit the input parameter to intentionally differ from the tool schema.","options":{}},{"role":"assistant","content":[{"type":"reasoning","text":"","options":{}},{"type":"text","text":"I'll help you test the error handling. I'll call the calculator tool while intentionally omitting the required `input` parameter.","options":{}},{"type":"tool-call","id":"toolu_01JstTa1BrCcddYHJXFH4pWB","name":"Calculator","params":{},"providerExecuted":false,"options":{}}],"options":{}},{"role":"tool","content":[{"type":"tool-result","id":"toolu_01JstTa1BrCcddYHJXFH4pWB","name":"Calculator","isFailure":true,"result":"MalformedOutput: { \"module\": \"Toolkit\", \"method\": \"Calculator.handle\", \"description\": \"Failed to decode tool call parameters for tool 'Calculator' from:\\n'{}'\", \"cause\": { readonly input: string }\n└─ [\"input\"]\n └─ is missing }\ncaused by:\nParseError: { readonly input: string }\n└─ [\"input\"]\n └─ is missing","providerExecuted":false,"options":{}}],"options":{"anthropic":{"cacheControl":{"ttl":"5m","type":"ephemeral"}}}}]},"response":[{"type":"response-metadata","id":"msg_011CddXif98pfvR7HTqWKkgk","modelId":"claude-opus-4-8","timestamp":"1970-01-01T00:00:00.000Z","metadata":{}},{"type":"text-start","id":"0","metadata":{}},{"type":"text-delta","id":"0","delta":"The","metadata":{}},{"type":"text-delta","id":"0","delta":" error handling worked as expected. Here's what happened when I called the calculator tool without the required `input` parameter:\n\n**Error type","metadata":{}},{"type":"text-delta","id":"0","delta":":** `MalformedOutput` / `ParseError`\n\n**Key details from the error:**\n- **Module:** `Toolkit`\n- **Method:","metadata":{}},{"type":"text-delta","id":"0","delta":"** `Calculator.handle`\n- **Description:** Failed to decode tool call parameters for tool 'Calculator' from `'{}'` (an empty par","metadata":{}},{"type":"text-delta","id":"0","delta":"ameter object)\n- **Cause:** The schema expects `{ readonly input: string }`, but the `[\"input\"]` field `","metadata":{}},{"type":"text-delta","id":"0","delta":"is missing`\n\n**Summary:** The parser correctly detected that the required `input` parameter was absent from the tool call. It validated the empty pay","metadata":{}},{"type":"text-delta","id":"0","delta":"load `{}` against the expected schema (which requires a `string` field named `input`) and rejected it with a clear \"","metadata":{}},{"type":"text-delta","id":"0","delta":"is missing\" message pointing directly to the offending field path (","metadata":{}},{"type":"text-delta","id":"0","delta":"`[\"input\"]`).\n\nThis confirms the parameter-parsing validation is functioning properly — it catches missing required fields before exec","metadata":{}},{"type":"text-delta","id":"0","delta":"ution and returns a descriptive, structured error rather than failing silently or crashing. Let me know if you'd like to","metadata":{}},{"type":"text-delta","id":"0","delta":" test other cases, such as passing an `input` of the wrong type (e.g., a number instead of a string) or including extra","metadata":{}},{"type":"text-delta","id":"0","delta":" unexpected parameters.","metadata":{}},{"type":"text-end","id":"0","metadata":{}},{"type":"finish","reason":"stop","usage":{"inputTokens":675,"outputTokens":394,"totalTokens":1069},"metadata":{"anthropic":{"usage":{"cache_creation":{"ephemeral_1h_input_tokens":0,"ephemeral_5m_input_tokens":0},"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"input_tokens":675,"output_tokens":1,"server_tool_use":null,"service_tier":"standard"}}}}]},{"parameters":{"model":"dxn:com.anthropic.model.claude-opus-4-8.default","stream":false,"tools":[]},"prompt":{"content":[{"role":"system","content":"<instructions>\n You are an agent that summarizes conversations.\n You will be given a conversation and you will need to summarize it to compact context.\n Your summary will be used by AI model to continue the conversation without seeing the original messages.\n Remove unnecessary details and focus on the most important information.\n Keep user requests, method of action, and progress.\n Keep the important objects and references in original format.\n It is very important you maintain unchanged ECHO DXNs and IDs of the objects.\n Output only the summary.\n</instructions>\n\n<example_output>\n Processing a flight booking confirmation email. The user requested a round-trip flight from Warsaw to Barcelona for March 15–22, 2026.\n\n Action taken:\n - Found existing booking object `echo:///01KHZWKQ9HPJYG147HV7CP5ARX` (type `echo:///01KHXAMH4F3V94RCYC7P7M2H0C` / `com.example.type.flight-booking`) matching confirmation code ABC123.\n - Updated the booking status from \"pending\" to \"confirmed\".\n - Added flight details: Outbound WAW→BCN Mar 15 08:30, Return BCN→WAW Mar 22 14:15, carrier LOT Polish Airlines.\n - Stored passenger info and seat assignments in notes.\n\n Booking details preserved: Confirmation ABC123, total 1,240 PLN, booked 2026-02-18, airline LOT Polish Airlines, flight numbers LO391/LO392.\n\n Status enum mapping for the booking schema: pending=Awaiting payment, confirmed=Booked, cancelled=Cancelled, completed=Flown.\n</example_output>\n\nTASK: Reply with the summary of the prior conversation.","options":{}},{"role":"user","content":"How many apples are in the basket?","options":{}},{"role":"assistant","content":"There are 10 apples in the basket.","options":{}},{"role":"user","content":"<instructions>\n You are an agent that summarizes conversations.\n You will be given a conversation and you will need to summarize it to compact context.\n Your summary will be used by AI model to continue the conversation without seeing the original messages.\n Remove unnecessary details and focus on the most important information.\n Keep user requests, method of action, and progress.\n Keep the important objects and references in original format.\n It is very important you maintain unchanged ECHO DXNs and IDs of the objects.\n Output only the summary.\n</instructions>\n\n<example_output>\n Processing a flight booking confirmation email. The user requested a round-trip flight from Warsaw to Barcelona for March 15–22, 2026.\n\n Action taken:\n - Found existing booking object `echo:///01KHZWKQ9HPJYG147HV7CP5ARX` (type `echo:///01KHXAMH4F3V94RCYC7P7M2H0C` / `com.example.type.flight-booking`) matching confirmation code ABC123.\n - Updated the booking status from \"pending\" to \"confirmed\".\n - Added flight details: Outbound WAW→BCN Mar 15 08:30, Return BCN→WAW Mar 22 14:15, carrier LOT Polish Airlines.\n - Stored passenger info and seat assignments in notes.\n\n Booking details preserved: Confirmation ABC123, total 1,240 PLN, booked 2026-02-18, airline LOT Polish Airlines, flight numbers LO391/LO392.\n\n Status enum mapping for the booking schema: pending=Awaiting payment, confirmed=Booked, cancelled=Cancelled, completed=Flown.\n</example_output>\n\nTASK: Reply with the summary of the prior conversation.","options":{}}]},"response":[{"type":"response-metadata","id":"msg_011CddXj7d6UFg6nW7uxL6e9","modelId":"claude-opus-4-8","timestamp":"1970-01-01T00:00:00.000Z","metadata":{}},{"type":"text","text":"The user asked how many apples are in the basket. I responded that there are 10 apples in the basket.","metadata":{}},{"type":"finish","reason":"stop","usage":{"inputTokens":1203,"outputTokens":32,"totalTokens":1235,"cachedInputTokens":0},"metadata":{"anthropic":{"usage":{"cache_creation":{"ephemeral_1h_input_tokens":0,"ephemeral_5m_input_tokens":0},"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"input_tokens":1203,"output_tokens":32,"server_tool_use":null,"service_tier":"standard"}}}}]},{"parameters":{"model":"dxn:com.anthropic.model.claude-opus-4-8.default","stream":true,"tools":[]},"prompt":{"content":[{"role":"assistant","content":"This is the continuation of a conversation that was compacted to preserve context-window space. Summary of what happened in this conversation previously: <summary>The user asked how many apples are in the basket. I responded that there are 10 apples in the basket.</summary>","options":{}},{"role":"user","content":"What did we talk about?","options":{"anthropic":{"cacheControl":{"ttl":"5m","type":"ephemeral"}}}}]},"response":[{"type":"response-metadata","id":"msg_011CddXjFrwNvzvvRHjWa7jB","modelId":"claude-opus-4-8","timestamp":"1970-01-01T00:00:00.000Z","metadata":{}},{"type":"reasoning-start","id":"0","metadata":{}},{"type":"reasoning-delta","id":"0","delta":"","metadata":{"anthropic":{"type":"thinking","signature":"EuQCCokBCBAYAipAJgFciVLEIER3spO7lmXR8YHlbruLkjlFn5TE7zeuMWBPeKBQNsYRCWtB/OtVUSVXMeEJAG6uozrx2N6Qhe2s3zIPY2xhdWRlLW9wdXMtNC04OABCCHRoaW5raW5nWiRlOTk3NGY0Mi0xMmMzLTRlYWUtOTA5OS01OGNiNGE1MzQyY2YSDG8D6w+b2XCS/R6ALBoMrweQhApyiNOQ8wzlIjD9y5lE2YzTR5/fSj8gWTm0GKOTARd+d/7jQ21+QGv13mbcvb/4qp4fPrQhWRJ9ncwqhwEZolLNwAIC/HJi5T1RPenZaq7zOat44mRLC9YXUcV+8tHVz/OwY5oPMuNMdpwbwJCANqti8uEFrXl5/bI21nqpbHCJ81gTO/GZUj9X6qZZAZlIPDjmQPSBUKa+5Kn/NL5MKThBpBxM+Ql/Wx/9PvOmmUpR8U35lHHu9Aj/PUheXOPPUjkaAK4YAQ=="}}},{"type":"reasoning-end","id":"0","metadata":{}},{"type":"text-start","id":"1","metadata":{}},{"type":"text-delta","id":"1","delta":"Based on our earlier conversation, you asked me how many apples are in the basket, and I told you there are 10 apples in the basket.\n\nIs there anything else you'd","metadata":{}},{"type":"text-delta","id":"1","delta":" like to know, or was there something specific about that you wanted to revisit?","metadata":{}},{"type":"text-end","id":"1","metadata":{}},{"type":"finish","reason":"stop","usage":{"inputTokens":114,"outputTokens":110,"totalTokens":224},"metadata":{"anthropic":{"usage":{"cache_creation":{"ephemeral_1h_input_tokens":0,"ephemeral_5m_input_tokens":0},"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"input_tokens":114,"output_tokens":3,"server_tool_use":null,"service_tier":"standard"}}}}]}]}
|
|
@@ -16,7 +16,7 @@ import { TestHelpers } from '@dxos/effect/testing';
|
|
|
16
16
|
import { log } from '@dxos/log';
|
|
17
17
|
import { Message } from '@dxos/types';
|
|
18
18
|
|
|
19
|
-
import { AssistantTestLayer } from '../testing';
|
|
19
|
+
import { AssistantTestLayer, runMemoizedTests } from '../testing';
|
|
20
20
|
|
|
21
21
|
// Define a calendar event artifact schema.
|
|
22
22
|
class CalendarEvent extends Type.makeObject<CalendarEvent>(DXN.make('com.example.type.calendarEvent', '0.1.0'))(
|
|
@@ -71,7 +71,7 @@ const TestLayer = Layer.empty.pipe(
|
|
|
71
71
|
Layer.provideMerge(toolkitLayer),
|
|
72
72
|
);
|
|
73
73
|
|
|
74
|
-
describe('AiRequest.Request', () => {
|
|
74
|
+
describe.skipIf(!runMemoizedTests())('AiRequest.Request', () => {
|
|
75
75
|
it.effect(
|
|
76
76
|
'no tools',
|
|
77
77
|
Effect.fnUntraced(
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as Tool from '@effect/ai/Tool';
|
|
6
|
+
import * as Toolkit from '@effect/ai/Toolkit';
|
|
7
|
+
import { describe, expect, it } from '@effect/vitest';
|
|
8
|
+
import * as Effect from 'effect/Effect';
|
|
9
|
+
import * as Layer from 'effect/Layer';
|
|
10
|
+
import * as Schema from 'effect/Schema';
|
|
11
|
+
|
|
12
|
+
import { OpaqueToolkit } from '@dxos/ai';
|
|
13
|
+
import { ScriptedLanguageModel } from '@dxos/ai/testing';
|
|
14
|
+
import { AiRequest, ToolExecutionServices } from '@dxos/assistant';
|
|
15
|
+
import { TestHelpers } from '@dxos/effect/testing';
|
|
16
|
+
import { EntityId } from '@dxos/keys';
|
|
17
|
+
import { ContentBlock, type Message } from '@dxos/types';
|
|
18
|
+
|
|
19
|
+
import { AssistantTestLayer } from '../testing';
|
|
20
|
+
|
|
21
|
+
const { text, toolCall, scriptedAiService } = ScriptedLanguageModel;
|
|
22
|
+
|
|
23
|
+
EntityId.dangerouslyDisableRandomness();
|
|
24
|
+
|
|
25
|
+
// A minimal echo tool: the deterministic developer code the loop invokes when the (scripted) model
|
|
26
|
+
// emits a tool call. Its handler runs for real, so a genuine tool-call → result → continue cycle is
|
|
27
|
+
// exercised without any live model.
|
|
28
|
+
const TestToolkit = Toolkit.make(
|
|
29
|
+
Tool.make('Echo', {
|
|
30
|
+
description: 'Returns its input value verbatim.',
|
|
31
|
+
parameters: {
|
|
32
|
+
value: Schema.String.annotations({ description: 'The value to echo.' }),
|
|
33
|
+
},
|
|
34
|
+
success: Schema.Struct({ value: Schema.String }),
|
|
35
|
+
failure: Schema.Never,
|
|
36
|
+
}),
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
const toolkitLayer = TestToolkit.toLayer({
|
|
40
|
+
Echo: ({ value }) => Effect.succeed({ value }),
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// Drives the real `AiRequest` loop against a scripted model instead of a live/memoized provider.
|
|
44
|
+
const testLayer = (turns: readonly ScriptedLanguageModel.ScriptedTurn[]) =>
|
|
45
|
+
Layer.empty.pipe(
|
|
46
|
+
Layer.provideMerge(ToolExecutionServices),
|
|
47
|
+
Layer.provideMerge(AssistantTestLayer({ aiService: scriptedAiService(turns) })),
|
|
48
|
+
Layer.provideMerge(toolkitLayer),
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
// Dimension D (harness / turn loop): deterministic developer code driven by a scripted model.
|
|
52
|
+
describe('AiRequest loop (scripted model)', () => {
|
|
53
|
+
it.effect(
|
|
54
|
+
'completes in one turn when the model emits no tool calls',
|
|
55
|
+
Effect.fnUntraced(
|
|
56
|
+
function* (_) {
|
|
57
|
+
const request = new AiRequest.Request();
|
|
58
|
+
const messages = yield* request.run({ prompt: 'Say hi.', history: [] });
|
|
59
|
+
|
|
60
|
+
expect(textOf(messages)).toContain('Hi there.');
|
|
61
|
+
expect(request.toolCalls).toEqual(0);
|
|
62
|
+
},
|
|
63
|
+
Effect.provide(testLayer([{ parts: [text('Hi there.')] }])),
|
|
64
|
+
TestHelpers.provideTestContext,
|
|
65
|
+
),
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
it.effect(
|
|
69
|
+
'executes a tool call, feeds the result back, then stops',
|
|
70
|
+
Effect.fnUntraced(
|
|
71
|
+
function* (_) {
|
|
72
|
+
const request = new AiRequest.Request();
|
|
73
|
+
const toolkit = yield* OpaqueToolkit.fromContext(TestToolkit);
|
|
74
|
+
const messages = yield* request.run({ toolkit, prompt: 'Echo hello.', history: [] });
|
|
75
|
+
|
|
76
|
+
expect(toolResultsOf(messages)).toHaveLength(1);
|
|
77
|
+
expect(textOf(messages)).toContain('Echoed the value.');
|
|
78
|
+
expect(request.toolCalls).toEqual(1);
|
|
79
|
+
},
|
|
80
|
+
Effect.provide(
|
|
81
|
+
testLayer([{ parts: [toolCall('Echo', { value: 'hello' })] }, { parts: [text('Echoed the value.')] }]),
|
|
82
|
+
),
|
|
83
|
+
TestHelpers.provideTestContext,
|
|
84
|
+
),
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
it.effect(
|
|
88
|
+
'iterates until the model stops, feeding each tool result back',
|
|
89
|
+
Effect.fnUntraced(
|
|
90
|
+
function* (_) {
|
|
91
|
+
const request = new AiRequest.Request();
|
|
92
|
+
const toolkit = yield* OpaqueToolkit.fromContext(TestToolkit);
|
|
93
|
+
const messages = yield* request.run({ toolkit, prompt: 'Echo twice.', history: [] });
|
|
94
|
+
|
|
95
|
+
expect(toolResultsOf(messages)).toHaveLength(2);
|
|
96
|
+
expect(textOf(messages)).toContain('All done.');
|
|
97
|
+
expect(request.toolCalls).toEqual(2);
|
|
98
|
+
},
|
|
99
|
+
Effect.provide(
|
|
100
|
+
testLayer([
|
|
101
|
+
{ parts: [toolCall('Echo', { value: 'first' })] },
|
|
102
|
+
{ parts: [toolCall('Echo', { value: 'second' })] },
|
|
103
|
+
{ parts: [text('All done.')] },
|
|
104
|
+
]),
|
|
105
|
+
),
|
|
106
|
+
TestHelpers.provideTestContext,
|
|
107
|
+
),
|
|
108
|
+
);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const textOf = (messages: readonly Message.Message[]): string =>
|
|
112
|
+
messages
|
|
113
|
+
.flatMap((message) => message.blocks)
|
|
114
|
+
.filter(ContentBlock.is('text'))
|
|
115
|
+
.map((block) => block.text)
|
|
116
|
+
.join('');
|
|
117
|
+
|
|
118
|
+
const toolResultsOf = (messages: readonly Message.Message[]) =>
|
|
119
|
+
messages.flatMap((message) => message.blocks).filter(ContentBlock.is('toolResult'));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"conversations":[{"parameters":{"model":"dxn:com.anthropic.model.claude-opus-4-8.default","stream":true,"tools":[]},"prompt":{"content":[{"role":"user","content":"Respond with your name inside an xml tag (use any tag name you like).","options":{"anthropic":{"cacheControl":{"ttl":"5m","type":"ephemeral"}}}}]},"response":[{"type":"response-metadata","id":"
|
|
1
|
+
{"conversations":[{"parameters":{"model":"dxn:com.anthropic.model.claude-opus-4-8.default","stream":true,"tools":[]},"prompt":{"content":[{"role":"user","content":"Respond with your name inside an xml tag (use any tag name you like).","options":{"anthropic":{"cacheControl":{"ttl":"5m","type":"ephemeral"}}}}]},"response":[{"type":"response-metadata","id":"msg_011CddXj7kXrgz3HvCYX4k1w","modelId":"claude-opus-4-8","timestamp":"1970-01-01T00:00:00.000Z","metadata":{}},{"type":"text-start","id":"0","metadata":{}},{"type":"text-delta","id":"0","delta":"<name","metadata":{}},{"type":"text-delta","id":"0","delta":">Claude</name>","metadata":{}},{"type":"text-end","id":"0","metadata":{}},{"type":"finish","reason":"stop","usage":{"inputTokens":28,"outputTokens":11,"totalTokens":39},"metadata":{"anthropic":{"usage":{"cache_creation":{"ephemeral_1h_input_tokens":0,"ephemeral_5m_input_tokens":0},"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"input_tokens":28,"output_tokens":2,"server_tool_use":null,"service_tier":"standard"}}}}]}]}
|
|
@@ -10,14 +10,14 @@ import { AiRequest, ToolExecutionServices } from '@dxos/assistant';
|
|
|
10
10
|
import { TestHelpers } from '@dxos/effect/testing';
|
|
11
11
|
import { log } from '@dxos/log';
|
|
12
12
|
|
|
13
|
-
import { AssistantTestLayer } from '../testing';
|
|
13
|
+
import { AssistantTestLayer, runMemoizedTests } from '../testing';
|
|
14
14
|
|
|
15
15
|
const TestLayer = Layer.empty.pipe(
|
|
16
16
|
Layer.provideMerge(ToolExecutionServices),
|
|
17
17
|
Layer.provideMerge(AssistantTestLayer({ tracing: 'noop' })),
|
|
18
18
|
);
|
|
19
19
|
|
|
20
|
-
describe('AiRequest.Request xml response', () => {
|
|
20
|
+
describe.skipIf(!runMemoizedTests())('AiRequest.Request xml response', () => {
|
|
21
21
|
// End-to-end regression: drive a real (memoized) LLM call via `AiRequest` and inspect the
|
|
22
22
|
// streamed blocks. Original bug: when the user prompted "respond with your name inside an
|
|
23
23
|
// xml tag", the model emitted `<name>Claude</name>` as text, but `AiParser.makeContentBlock`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"conversations":[{"parameters":{"model":"dxn:com.anthropic.model.claude-opus-4-8.default","stream":true,"tools":[{"name":"read-name","description":"Reads the name of an organization.","inputSchema":{"type":"object","required":["org"],"properties":{"org":{"type":"string","description":"The URI of the referenced object. Protocols accepted:\n- echo://01KG7R1ZXWFMWQ4DA1Q6TN1DG4. \n- space ID, object ID tuple (spaceID:objectID)\n- Only object ID that is assumed to be in the current space (01KG7R1ZXWFMWQ4DA1Q6TN1DG4)\n- dxn:org.dxos.type.calendar:0.1.0 -- named entity from the registry. Absoutely make sure it actually exists in the registry.\n\nURIs can be prepended with an @ symbol for compatibility with in-text references.","examples":["echo://01KG7R1ZXWFMWQ4DA1Q6TN1DG4","@echo://01KG7R1ZXWFMWQ4DA1Q6TN1DG4","BM3FSHFOMJCHCG5QW7JTVKGYABD2GAA7G:01KG7R1ZXWFMWQ4DA1Q6TN1DG4","01KG7R1ZXWFMWQ4DA1Q6TN1DG4","dxn:org.dxos.type.calendar:0.1.0","@dxn:org.dxos.type.calendar:0.1.0"]}},"additionalProperties":false}}]},"prompt":{"content":[{"role":"user","content":"What is the name of the organization? 01JGFJJZ00G0WKQSJGMAKCNT8A","options":{"anthropic":{"cacheControl":{"ttl":"5m","type":"ephemeral"}}}}]},"response":[{"type":"response-metadata","id":"
|
|
1
|
+
{"conversations":[{"parameters":{"model":"dxn:com.anthropic.model.claude-opus-4-8.default","stream":true,"tools":[{"name":"read-name","description":"Reads the name of an organization.","inputSchema":{"type":"object","required":["org"],"properties":{"org":{"type":"string","description":"The URI of the referenced object. Protocols accepted:\n- echo://01KG7R1ZXWFMWQ4DA1Q6TN1DG4. \n- space ID, object ID tuple (spaceID:objectID)\n- Only object ID that is assumed to be in the current space (01KG7R1ZXWFMWQ4DA1Q6TN1DG4)\n- dxn:org.dxos.type.calendar:0.1.0 -- named entity from the registry. Absoutely make sure it actually exists in the registry.\n\nURIs can be prepended with an @ symbol for compatibility with in-text references.","examples":["echo://01KG7R1ZXWFMWQ4DA1Q6TN1DG4","@echo://01KG7R1ZXWFMWQ4DA1Q6TN1DG4","BM3FSHFOMJCHCG5QW7JTVKGYABD2GAA7G:01KG7R1ZXWFMWQ4DA1Q6TN1DG4","01KG7R1ZXWFMWQ4DA1Q6TN1DG4","dxn:org.dxos.type.calendar:0.1.0","@dxn:org.dxos.type.calendar:0.1.0"]}},"additionalProperties":false}}]},"prompt":{"content":[{"role":"user","content":"What is the name of the organization? 01JGFJJZ00G0WKQSJGMAKCNT8A","options":{"anthropic":{"cacheControl":{"ttl":"5m","type":"ephemeral"}}}}]},"response":[{"type":"response-metadata","id":"msg_011CddXjz8AxxQjU1XpwL9jw","modelId":"claude-opus-4-8","timestamp":"1970-01-01T00:00:00.000Z","metadata":{}},{"type":"text-start","id":"0","metadata":{}},{"type":"text-delta","id":"0","delta":"I","metadata":{}},{"type":"text-delta","id":"0","delta":"'ll look up the organization name for you.","metadata":{}},{"type":"text-end","id":"0","metadata":{}},{"type":"tool-params-start","id":"toolu_019oK6FrUL2amC7eZ4keXH8m","name":"read-name","providerExecuted":false,"metadata":{}},{"type":"tool-params-delta","id":"toolu_019oK6FrUL2amC7eZ4keXH8m","delta":"","metadata":{}},{"type":"tool-params-delta","id":"toolu_019oK6FrUL2amC7eZ4keXH8m","delta":"{\"o","metadata":{}},{"type":"tool-params-delta","id":"toolu_019oK6FrUL2amC7eZ4keXH8m","delta":"rg","metadata":{}},{"type":"tool-params-delta","id":"toolu_019oK6FrUL2amC7eZ4keXH8m","delta":"\": \"01JGFJ","metadata":{}},{"type":"tool-params-delta","id":"toolu_019oK6FrUL2amC7eZ4keXH8m","delta":"JZ00G0","metadata":{}},{"type":"tool-params-delta","id":"toolu_019oK6FrUL2amC7eZ4keXH8m","delta":"WKQSJ","metadata":{}},{"type":"tool-params-delta","id":"toolu_019oK6FrUL2amC7eZ4keXH8m","delta":"GMAKCNT8A\"}","metadata":{}},{"type":"tool-params-end","id":"toolu_019oK6FrUL2amC7eZ4keXH8m","metadata":{}},{"type":"finish","reason":"tool-calls","usage":{"inputTokens":838,"outputTokens":84,"totalTokens":922},"metadata":{"anthropic":{"usage":{"cache_creation":{"ephemeral_1h_input_tokens":0,"ephemeral_5m_input_tokens":0},"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"input_tokens":838,"output_tokens":1,"server_tool_use":null,"service_tier":"standard"}}}}]},{"parameters":{"model":"dxn:com.anthropic.model.claude-opus-4-8.default","stream":true,"tools":[{"name":"read-name","description":"Reads the name of an organization.","inputSchema":{"type":"object","required":["org"],"properties":{"org":{"type":"string","description":"The URI of the referenced object. Protocols accepted:\n- echo://01KG7R1ZXWFMWQ4DA1Q6TN1DG4. \n- space ID, object ID tuple (spaceID:objectID)\n- Only object ID that is assumed to be in the current space (01KG7R1ZXWFMWQ4DA1Q6TN1DG4)\n- dxn:org.dxos.type.calendar:0.1.0 -- named entity from the registry. Absoutely make sure it actually exists in the registry.\n\nURIs can be prepended with an @ symbol for compatibility with in-text references.","examples":["echo://01KG7R1ZXWFMWQ4DA1Q6TN1DG4","@echo://01KG7R1ZXWFMWQ4DA1Q6TN1DG4","BM3FSHFOMJCHCG5QW7JTVKGYABD2GAA7G:01KG7R1ZXWFMWQ4DA1Q6TN1DG4","01KG7R1ZXWFMWQ4DA1Q6TN1DG4","dxn:org.dxos.type.calendar:0.1.0","@dxn:org.dxos.type.calendar:0.1.0"]}},"additionalProperties":false}}]},"prompt":{"content":[{"role":"user","content":"What is the name of the organization? 01JGFJJZ00G0WKQSJGMAKCNT8A","options":{}},{"role":"assistant","content":[{"type":"text","text":"I'll look up the organization name for you.","options":{}},{"type":"tool-call","id":"toolu_019oK6FrUL2amC7eZ4keXH8m","name":"read-name","params":{"org":"01JGFJJZ00G0WKQSJGMAKCNT8A"},"providerExecuted":false,"options":{}}],"options":{}},{"role":"tool","content":[{"type":"tool-result","id":"toolu_019oK6FrUL2amC7eZ4keXH8m","name":"read-name","isFailure":false,"result":"BlueYard","providerExecuted":false,"options":{}}],"options":{"anthropic":{"cacheControl":{"ttl":"5m","type":"ephemeral"}}}}]},"response":[{"type":"response-metadata","id":"msg_011CddXk8c9wqoVmMGABnPEc","modelId":"claude-opus-4-8","timestamp":"1970-01-01T00:00:00.000Z","metadata":{}},{"type":"text-start","id":"0","metadata":{}},{"type":"text-delta","id":"0","delta":"The","metadata":{}},{"type":"text-delta","id":"0","delta":" name of the organization is **BlueYard**.","metadata":{}},{"type":"text-end","id":"0","metadata":{}},{"type":"finish","reason":"stop","usage":{"inputTokens":934,"outputTokens":17,"totalTokens":951},"metadata":{"anthropic":{"usage":{"cache_creation":{"ephemeral_1h_input_tokens":0,"ephemeral_5m_input_tokens":0},"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"input_tokens":934,"output_tokens":1,"server_tool_use":null,"service_tier":"standard"}}}}]}]}
|
package/src/functions.test.ts
CHANGED
|
@@ -16,7 +16,7 @@ import { TestHelpers } from '@dxos/effect/testing';
|
|
|
16
16
|
import { DXN, EntityId } from '@dxos/keys';
|
|
17
17
|
import { Organization } from '@dxos/types';
|
|
18
18
|
|
|
19
|
-
import { AssistantTestLayer } from './testing';
|
|
19
|
+
import { AssistantTestLayer, runMemoizedTests } from './testing';
|
|
20
20
|
|
|
21
21
|
EntityId.dangerouslyDisableRandomness();
|
|
22
22
|
|
|
@@ -60,7 +60,7 @@ const TestLayer = Layer.empty.pipe(
|
|
|
60
60
|
),
|
|
61
61
|
);
|
|
62
62
|
|
|
63
|
-
describe('Research', () => {
|
|
63
|
+
describe.skipIf(!runMemoizedTests())('Research', () => {
|
|
64
64
|
it.effect(
|
|
65
65
|
'call a function with a ref input',
|
|
66
66
|
Effect.fnUntraced(
|
package/src/index.ts
CHANGED
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
ProcessManager,
|
|
33
33
|
ProcessMonitor,
|
|
34
34
|
RemoteProcessManager,
|
|
35
|
+
RemoteTraceMonitor,
|
|
35
36
|
TriggerDispatcher,
|
|
36
37
|
TriggerStateStore,
|
|
37
38
|
configuredCredentialsLayer,
|
|
@@ -100,6 +101,7 @@ export type AssistantTestServices =
|
|
|
100
101
|
| OpaqueToolkit.OpaqueToolkitProvider
|
|
101
102
|
| Operation.Service
|
|
102
103
|
| ProcessManager.Service
|
|
104
|
+
| ProcessManager.ProcessOperationInvoker.Service
|
|
103
105
|
| Process.ProcessMonitorService
|
|
104
106
|
| AtomRegistry.AtomRegistry
|
|
105
107
|
| OperationHandlerSet.OperationHandlerProvider
|
|
@@ -132,11 +134,15 @@ export const AssistantTestLayer = (
|
|
|
132
134
|
// sharing a late-bound holder: the resolver reads the manager lazily (only at resolution time,
|
|
133
135
|
// when the manager exists), and a downstream layer fills the holder once it is built.
|
|
134
136
|
const processManagerHolder: ProcessManagerHolder = {};
|
|
137
|
+
const agentServiceHolder: AgentServiceHolder = {};
|
|
135
138
|
|
|
136
139
|
return Layer.empty.pipe(
|
|
140
|
+
// Captures must sit above the layers they read (a provideMerge chain feeds upward).
|
|
141
|
+
Layer.provideMerge(captureAgentService(agentServiceHolder)),
|
|
137
142
|
Layer.provideMerge(ProcessManager.ProcessOperationInvoker.layer),
|
|
138
143
|
Layer.provideMerge(ProcessMonitor.layer),
|
|
139
144
|
Layer.provideMerge(RemoteProcessManager.layerNoop),
|
|
145
|
+
Layer.provideMerge(RemoteTraceMonitor.layerNoop),
|
|
140
146
|
Layer.provideMerge(AgentServiceRuntime.layer(agentOptions)),
|
|
141
147
|
Layer.provideMerge(Trace.testTraceService({ meta: { processName: 'test' } })),
|
|
142
148
|
// Order matters: in a `provideMerge` chain each layer's *requirements* are satisfied only by
|
|
@@ -145,7 +151,7 @@ export const AssistantTestLayer = (
|
|
|
145
151
|
// the holder), so they must appear in exactly this order.
|
|
146
152
|
Layer.provideMerge(captureProcessManager(processManagerHolder)),
|
|
147
153
|
Layer.provideMerge(ProcessManager.layer({ idGenerator: ProcessManager.SequentialIdGenerator })),
|
|
148
|
-
Layer.provideMerge(AssistantTestServiceResolverLayer(options, processManagerHolder)),
|
|
154
|
+
Layer.provideMerge(AssistantTestServiceResolverLayer(options, processManagerHolder, agentServiceHolder)),
|
|
149
155
|
Layer.provideMerge(AiService.model(DXN.getName(resolvedModel), { provider: resolvedProvider })),
|
|
150
156
|
Layer.provideMerge(AssistantTestTracingLayer(options.tracing ?? 'noop')),
|
|
151
157
|
Layer.provideMerge(
|
|
@@ -166,6 +172,19 @@ interface ProcessManagerHolder {
|
|
|
166
172
|
current?: Context.Tag.Service<ProcessManager.Service>;
|
|
167
173
|
}
|
|
168
174
|
|
|
175
|
+
/** Late-bound reference to the {@link AgentService.AgentService}, filled once the service is built. */
|
|
176
|
+
interface AgentServiceHolder {
|
|
177
|
+
current?: Context.Tag.Service<AgentService.AgentService>;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** Fills the {@link AgentServiceHolder}, letting the resolver serve operations that relay into agent sessions. */
|
|
181
|
+
const captureAgentService = (holder: AgentServiceHolder): Layer.Layer<never, never, AgentService.AgentService> =>
|
|
182
|
+
Layer.effectDiscard(
|
|
183
|
+
Effect.gen(function* () {
|
|
184
|
+
holder.current = yield* AgentService.AgentService;
|
|
185
|
+
}),
|
|
186
|
+
);
|
|
187
|
+
|
|
169
188
|
/** Fills the {@link ProcessManagerHolder} once the manager is available, breaking the build cycle. */
|
|
170
189
|
const captureProcessManager = (holder: ProcessManagerHolder): Layer.Layer<never, never, ProcessManager.Service> =>
|
|
171
190
|
Layer.effectDiscard(
|
|
@@ -180,6 +199,7 @@ const captureProcessManager = (holder: ProcessManagerHolder): Layer.Layer<never,
|
|
|
180
199
|
export const AssistantTestServiceResolverLayer = (
|
|
181
200
|
{ extraServices = Layer.empty }: Pick<TestLayerOptions, 'extraServices'>,
|
|
182
201
|
processManagerHolder: ProcessManagerHolder,
|
|
202
|
+
agentServiceHolder: AgentServiceHolder = {},
|
|
183
203
|
) =>
|
|
184
204
|
Layer.scoped(
|
|
185
205
|
ServiceResolver.ServiceResolver,
|
|
@@ -207,6 +227,17 @@ export const AssistantTestServiceResolverLayer = (
|
|
|
207
227
|
return yield* Harness.make({ conversation: context.conversation, processManager, runtime });
|
|
208
228
|
}).pipe(Effect.provide(services)),
|
|
209
229
|
),
|
|
230
|
+
ServiceResolver.succeed(AgentService.AgentService, () =>
|
|
231
|
+
Effect.gen(function* () {
|
|
232
|
+
// Read lazily (like the process manager): filled by `captureAgentService` before any
|
|
233
|
+
// operation resolution runs.
|
|
234
|
+
const agentService = agentServiceHolder.current;
|
|
235
|
+
if (!agentService) {
|
|
236
|
+
return yield* Effect.fail(new ServiceNotAvailableError(AgentService.AgentService.key));
|
|
237
|
+
}
|
|
238
|
+
return agentService;
|
|
239
|
+
}),
|
|
240
|
+
),
|
|
210
241
|
yield* ServiceResolver.fromRequirements(
|
|
211
242
|
Database.Service,
|
|
212
243
|
OpaqueToolkit.OpaqueToolkitProvider,
|
package/src/testing/index.ts
CHANGED