@elevasis/sdk 1.41.1 → 1.42.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/cli.cjs
CHANGED
|
@@ -45834,7 +45834,7 @@ function wrapAction(commandName, fn) {
|
|
|
45834
45834
|
// package.json
|
|
45835
45835
|
var package_default = {
|
|
45836
45836
|
name: "@elevasis/sdk",
|
|
45837
|
-
version: "1.
|
|
45837
|
+
version: "1.42.0",
|
|
45838
45838
|
description: "SDK for building Elevasis organization resources",
|
|
45839
45839
|
type: "module",
|
|
45840
45840
|
bin: {
|
package/dist/test-utils/index.js
CHANGED
|
@@ -4461,26 +4461,24 @@ function resolveSecurityLevel(config2) {
|
|
|
4461
4461
|
|
|
4462
4462
|
// ../core/src/execution/engine/agent/reasoning/prompt-sections/base-actions.ts
|
|
4463
4463
|
function buildBaseActionsPrompt(includeMessageAction, includeNavigateKnowledge) {
|
|
4464
|
-
|
|
4465
|
-
const actions = ["1. tool-call (call a tool)"];
|
|
4466
|
-
if (includeMessageAction) {
|
|
4467
|
-
actionCount++;
|
|
4468
|
-
actions.push(`${actionCount}. message (send message to user)`);
|
|
4469
|
-
}
|
|
4464
|
+
const actionNames = ["tool-call (call a tool)"];
|
|
4470
4465
|
if (includeNavigateKnowledge) {
|
|
4471
|
-
|
|
4472
|
-
actions.push(`${actionCount}. navigate-knowledge (load knowledge node)`);
|
|
4466
|
+
actionNames.push("navigate-knowledge (load knowledge node)");
|
|
4473
4467
|
}
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
const
|
|
4468
|
+
actionNames.push("complete (finish task)");
|
|
4469
|
+
const actionsList = actionNames.map((name, index2) => `${index2 + 1}. ${name}`).join("\n");
|
|
4470
|
+
const actionCount = actionNames.length;
|
|
4477
4471
|
return `# CORE AGENT INSTRUCTIONS
|
|
4478
4472
|
|
|
4479
|
-
You are an AI agent. Your response is captured as structured output. Two fields are required on
|
|
4473
|
+
You are an AI agent. Your response is captured as structured output. ${includeMessageAction ? "Three fields are required" : "Two fields are required"} on
|
|
4480
4474
|
every response:
|
|
4481
4475
|
|
|
4482
4476
|
- **reasoning** -- your thought process, as plain prose.
|
|
4483
|
-
- **nextActions** -- the actions to execute
|
|
4477
|
+
- **nextActions** -- the actions to execute.${includeMessageAction ? `
|
|
4478
|
+
- **message** -- your reply to the user, as plain prose. This is the ONLY field the user sees.
|
|
4479
|
+
Whatever you decide to say, it goes here. Use an empty string only when this iteration just calls
|
|
4480
|
+
tools and you genuinely have nothing to tell the user yet -- an empty message ends the turn in
|
|
4481
|
+
silence.` : ""}
|
|
4484
4482
|
|
|
4485
4483
|
**reasoning is prose, not JSON.** Do not write field names, braces, or a response object inside it,
|
|
4486
4484
|
and never continue the response envelope in the reasoning text -- nextActions is a separate field
|
|
@@ -4491,14 +4489,16 @@ that you fill separately. A response carrying reasoning alone is discarded and r
|
|
|
4491
4489
|
${actionsList}
|
|
4492
4490
|
|
|
4493
4491
|
**Formats:**
|
|
4494
|
-
- tool-call: { "type": "tool-call", "id": "unique-id", "name": "tool_name", "input": {...} }${
|
|
4495
|
-
- message: { "type": "message", "text": "Your message" }` : ""}${includeNavigateKnowledge ? `
|
|
4492
|
+
- tool-call: { "type": "tool-call", "id": "unique-id", "name": "tool_name", "input": {...} }${includeNavigateKnowledge ? `
|
|
4496
4493
|
- navigate-knowledge: { "type": "navigate-knowledge", "id": "unique-id", "nodeId": "node-name" }` : ""}
|
|
4497
4494
|
- complete: { "type": "complete" }
|
|
4498
|
-
|
|
4495
|
+
${includeMessageAction ? `
|
|
4496
|
+
Talking to the user is NOT an action. There is no message action -- put your reply in the
|
|
4497
|
+
**message** field beside nextActions.
|
|
4498
|
+
` : ""}
|
|
4499
4499
|
## Execution Flow
|
|
4500
4500
|
|
|
4501
|
-
1. You respond with reasoning + actions
|
|
4501
|
+
1. You respond with reasoning + actions${includeMessageAction ? " + your message to the user" : ""}
|
|
4502
4502
|
2. System executes actions (tool calls run **in parallel**)
|
|
4503
4503
|
3. Tool results automatically appear in your next iteration
|
|
4504
4504
|
4. You see results and decide: more work needed? Or complete?
|
|
@@ -4510,10 +4510,10 @@ ${actionsList}
|
|
|
4510
4510
|
- Dependent operations need separate iterations (tool B needs tool A's result)
|
|
4511
4511
|
- "complete" cannot mix with navigate-knowledge${includeNavigateKnowledge ? "" : " (when available)"}
|
|
4512
4512
|
- "complete" can mix with tool-call when the tool is a fire-and-forget side effect and you do not need its result before ending${includeMessageAction ? `
|
|
4513
|
-
- Always
|
|
4514
|
-
-
|
|
4515
|
-
- When you have your answer,
|
|
4516
|
-
- Never repeat or rephrase the same answer across iterations. One clear answer, then complete
|
|
4513
|
+
- Always fill message before completing -- it is the only field the user sees, and completing with it empty ends the turn in silence
|
|
4514
|
+
- message holds one reply. Write the whole reply in it; do not split a reply across iterations
|
|
4515
|
+
- When you have your answer, put it in message and include complete in the SAME iteration. Never reply on one iteration then complete on a later one
|
|
4516
|
+
- Never repeat or rephrase the same answer across iterations. One clear answer, then complete` : ""}
|
|
4517
4517
|
|
|
4518
4518
|
**Use "complete" when:**
|
|
4519
4519
|
- Task finished successfully
|
|
@@ -4527,27 +4527,27 @@ ${actionsList}
|
|
|
4527
4527
|
|
|
4528
4528
|
## Examples
|
|
4529
4529
|
|
|
4530
|
-
Each example shows the
|
|
4530
|
+
Each example shows the field values, not a JSON document to copy.
|
|
4531
4531
|
|
|
4532
4532
|
### Example 1: Simple Task (No Tools)
|
|
4533
|
-
- reasoning: Simple greeting, no tools needed
|
|
4534
|
-
- nextActions: [
|
|
4533
|
+
- reasoning: Simple greeting, no tools needed.${includeMessageAction ? "\n- message: Hi! How can I help?" : ""}
|
|
4534
|
+
- nextActions: [{ "type": "complete" }]
|
|
4535
4535
|
|
|
4536
4536
|
### Example 2: Tool Usage (Two Iterations)
|
|
4537
4537
|
|
|
4538
4538
|
**Iteration 1 - Call tool (NO complete - waiting for results):**
|
|
4539
|
-
- reasoning: User asked for time. Calling get_time tool
|
|
4540
|
-
- nextActions: [
|
|
4539
|
+
- reasoning: User asked for time. Calling get_time tool.${includeMessageAction ? "\n- message: Checking the time..." : ""}
|
|
4540
|
+
- nextActions: [{ "type": "tool-call", "id": "t1", "name": "get_time", "input": { "timezone": "UTC" } }]
|
|
4541
4541
|
|
|
4542
4542
|
**Iteration 2 - Tool result received, now complete:**
|
|
4543
|
-
- reasoning: Got time result: 12:00 PM UTC. Task done.
|
|
4544
|
-
- nextActions: [
|
|
4543
|
+
- reasoning: Got time result: 12:00 PM UTC. Task done.${includeMessageAction ? "\n- message: The current time is 12:00 PM UTC." : ""}
|
|
4544
|
+
- nextActions: [{ "type": "complete" }]
|
|
4545
4545
|
|
|
4546
4546
|
### Example 3: Parallel Tool Calls (Independent Operations)
|
|
4547
4547
|
When tools don't depend on each other, batch them for faster execution.
|
|
4548
4548
|
|
|
4549
|
-
- reasoning: User wants time AND weather. Independent operations - calling both in parallel
|
|
4550
|
-
- nextActions: [
|
|
4549
|
+
- reasoning: User wants time AND weather. Independent operations - calling both in parallel.${includeMessageAction ? "\n- message: Getting time and weather..." : ""}
|
|
4550
|
+
- nextActions: [{ "type": "tool-call", "id": "t1", "name": "get_time", "input": {} }, { "type": "tool-call", "id": "w1", "name": "get_weather", "input": { "city": "NYC" } }]
|
|
4551
4551
|
|
|
4552
4552
|
### Example 4: Dependent Operations (Separate Iterations Required)
|
|
4553
4553
|
|
|
@@ -4557,8 +4557,8 @@ When tools don't depend on each other, batch them for faster execution.
|
|
|
4557
4557
|
Problem: update_user needs userId from search_user result!
|
|
4558
4558
|
|
|
4559
4559
|
**\u2705 CORRECT - Iteration 1 (get the dependency):**
|
|
4560
|
-
- reasoning: Need to find user first before updating
|
|
4561
|
-
- nextActions: [
|
|
4560
|
+
- reasoning: Need to find user first before updating.${includeMessageAction ? "\n- message: Looking up user..." : ""}
|
|
4561
|
+
- nextActions: [{ "type": "tool-call", "id": "1", "name": "search_user", "input": { "email": "user@example.com" } }]
|
|
4562
4562
|
|
|
4563
4563
|
**\u2705 CORRECT - Iteration 2 (use the result):**
|
|
4564
4564
|
- reasoning: Found userId: user_123. Now can update.
|
|
@@ -4641,7 +4641,7 @@ function buildToolsPrompt(tools) {
|
|
|
4641
4641
|
section += '{\n "type": "tool-call",\n "id": "unique-id",\n "name": "tool-name",\n "input": { /* tool input matching schema */ }\n}\n\n';
|
|
4642
4642
|
section += "**IMPORTANT RULES:**\n";
|
|
4643
4643
|
section += '1. "complete" CANNOT mix with navigate-knowledge actions in the same response\n';
|
|
4644
|
-
section += '2. "
|
|
4644
|
+
section += '2. The "message" field CAN be filled on the same response that completes - always pair your final message with complete in the same iteration\n';
|
|
4645
4645
|
section += '3. "complete" CAN mix with fire-and-forget tool-call actions when you do not need their results\n';
|
|
4646
4646
|
section += "4. To use tools and inspect their results, return ONLY tool-call actions, then wait for results in the next iteration\n";
|
|
4647
4647
|
section += "5. After receiving tool results, you can either call more tools OR complete with final answer\n";
|
|
@@ -5204,6 +5204,7 @@ var MemoryOperationsSchema = z.object({
|
|
|
5204
5204
|
});
|
|
5205
5205
|
var AgentIterationOutputSchema = z.object({
|
|
5206
5206
|
reasoning: z.string(),
|
|
5207
|
+
message: z.string().optional(),
|
|
5207
5208
|
memoryOps: MemoryOperationsSchema.optional(),
|
|
5208
5209
|
nextActions: z.array(AgentActionSchema)
|
|
5209
5210
|
});
|
|
@@ -5252,6 +5253,17 @@ ${memory.framing}` : memory.framing },
|
|
|
5252
5253
|
}
|
|
5253
5254
|
return messages;
|
|
5254
5255
|
}
|
|
5256
|
+
function withSynthesizedMessage(nextActions, message) {
|
|
5257
|
+
const text = message?.trim();
|
|
5258
|
+
if (!text) {
|
|
5259
|
+
return nextActions;
|
|
5260
|
+
}
|
|
5261
|
+
const alreadyPresent = nextActions.some((action) => action.type === "message" && action.text.trim() === text);
|
|
5262
|
+
if (alreadyPresent) {
|
|
5263
|
+
return nextActions;
|
|
5264
|
+
}
|
|
5265
|
+
return [{ type: "message", text }, ...nextActions];
|
|
5266
|
+
}
|
|
5255
5267
|
async function callLLMForAgentIteration(adapter, request) {
|
|
5256
5268
|
validateTokenConfiguration(request.model, request.constraints.maxOutputTokens);
|
|
5257
5269
|
const messages = buildAgentMessages(
|
|
@@ -5289,7 +5301,7 @@ async function callLLMForAgentIteration(adapter, request) {
|
|
|
5289
5301
|
return {
|
|
5290
5302
|
reasoning: validated.reasoning,
|
|
5291
5303
|
memoryOps: validated.memoryOps,
|
|
5292
|
-
nextActions: validated.nextActions
|
|
5304
|
+
nextActions: withSynthesizedMessage(validated.nextActions, validated.message)
|
|
5293
5305
|
};
|
|
5294
5306
|
} catch (error) {
|
|
5295
5307
|
flowLog("agent.iteration.validationFailed", {
|
|
@@ -5297,6 +5309,7 @@ async function callLLMForAgentIteration(adapter, request) {
|
|
|
5297
5309
|
missingRequired: ["reasoning", "nextActions"].filter(
|
|
5298
5310
|
(k2) => !(typeof response.output === "object" && response.output !== null && k2 in response.output)
|
|
5299
5311
|
),
|
|
5312
|
+
messagePresent: typeof response.output === "object" && response.output !== null && typeof response.output.message === "string",
|
|
5300
5313
|
zodIssues: error instanceof ZodError ? error.issues.map((i) => ({ path: i.path.join("."), code: i.code })) : void 0
|
|
5301
5314
|
});
|
|
5302
5315
|
throw new AgentOutputValidationError("Agent iteration output validation failed", {
|
|
@@ -5374,17 +5387,6 @@ function buildIterationResponseSchema(tools, includeMessageAction, includeNaviga
|
|
|
5374
5387
|
required: ["type"],
|
|
5375
5388
|
additionalProperties: false
|
|
5376
5389
|
});
|
|
5377
|
-
if (includeMessageAction) {
|
|
5378
|
-
actionSchemas.push({
|
|
5379
|
-
type: "object",
|
|
5380
|
-
properties: {
|
|
5381
|
-
type: { type: "string", enum: ["message"] },
|
|
5382
|
-
text: { type: "string" }
|
|
5383
|
-
},
|
|
5384
|
-
required: ["type", "text"],
|
|
5385
|
-
additionalProperties: false
|
|
5386
|
-
});
|
|
5387
|
-
}
|
|
5388
5390
|
if (includeNavigateKnowledge) {
|
|
5389
5391
|
actionSchemas.push({
|
|
5390
5392
|
type: "object",
|
|
@@ -5403,9 +5405,15 @@ function buildIterationResponseSchema(tools, includeMessageAction, includeNaviga
|
|
|
5403
5405
|
items: {
|
|
5404
5406
|
anyOf: actionSchemas
|
|
5405
5407
|
}
|
|
5406
|
-
}
|
|
5407
|
-
reasoning: { type: "string", description: "Your reasoning process" }
|
|
5408
|
+
}
|
|
5408
5409
|
};
|
|
5410
|
+
if (includeMessageAction) {
|
|
5411
|
+
properties.message = {
|
|
5412
|
+
type: "string",
|
|
5413
|
+
description: "Your reply to the user, as plain prose. This is the ONLY field the user sees. Use an empty string only when this iteration just calls tools and you have nothing to say yet."
|
|
5414
|
+
};
|
|
5415
|
+
}
|
|
5416
|
+
properties.reasoning = { type: "string", description: "Your reasoning process" };
|
|
5409
5417
|
if (includeMemoryOps) {
|
|
5410
5418
|
properties.memoryOps = {
|
|
5411
5419
|
type: "object",
|
|
@@ -5436,7 +5444,7 @@ function buildIterationResponseSchema(tools, includeMessageAction, includeNaviga
|
|
|
5436
5444
|
return {
|
|
5437
5445
|
type: "object",
|
|
5438
5446
|
properties,
|
|
5439
|
-
required: ["nextActions", "reasoning"],
|
|
5447
|
+
required: includeMessageAction ? ["nextActions", "message", "reasoning"] : ["nextActions", "reasoning"],
|
|
5440
5448
|
additionalProperties: false
|
|
5441
5449
|
};
|
|
5442
5450
|
}
|
package/dist/worker/index.js
CHANGED
|
@@ -2583,26 +2583,24 @@ function resolveSecurityLevel(config) {
|
|
|
2583
2583
|
|
|
2584
2584
|
// ../core/src/execution/engine/agent/reasoning/prompt-sections/base-actions.ts
|
|
2585
2585
|
function buildBaseActionsPrompt(includeMessageAction, includeNavigateKnowledge) {
|
|
2586
|
-
|
|
2587
|
-
const actions = ["1. tool-call (call a tool)"];
|
|
2588
|
-
if (includeMessageAction) {
|
|
2589
|
-
actionCount++;
|
|
2590
|
-
actions.push(`${actionCount}. message (send message to user)`);
|
|
2591
|
-
}
|
|
2586
|
+
const actionNames = ["tool-call (call a tool)"];
|
|
2592
2587
|
if (includeNavigateKnowledge) {
|
|
2593
|
-
|
|
2594
|
-
actions.push(`${actionCount}. navigate-knowledge (load knowledge node)`);
|
|
2588
|
+
actionNames.push("navigate-knowledge (load knowledge node)");
|
|
2595
2589
|
}
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
const
|
|
2590
|
+
actionNames.push("complete (finish task)");
|
|
2591
|
+
const actionsList = actionNames.map((name, index) => `${index + 1}. ${name}`).join("\n");
|
|
2592
|
+
const actionCount = actionNames.length;
|
|
2599
2593
|
return `# CORE AGENT INSTRUCTIONS
|
|
2600
2594
|
|
|
2601
|
-
You are an AI agent. Your response is captured as structured output. Two fields are required on
|
|
2595
|
+
You are an AI agent. Your response is captured as structured output. ${includeMessageAction ? "Three fields are required" : "Two fields are required"} on
|
|
2602
2596
|
every response:
|
|
2603
2597
|
|
|
2604
2598
|
- **reasoning** -- your thought process, as plain prose.
|
|
2605
|
-
- **nextActions** -- the actions to execute
|
|
2599
|
+
- **nextActions** -- the actions to execute.${includeMessageAction ? `
|
|
2600
|
+
- **message** -- your reply to the user, as plain prose. This is the ONLY field the user sees.
|
|
2601
|
+
Whatever you decide to say, it goes here. Use an empty string only when this iteration just calls
|
|
2602
|
+
tools and you genuinely have nothing to tell the user yet -- an empty message ends the turn in
|
|
2603
|
+
silence.` : ""}
|
|
2606
2604
|
|
|
2607
2605
|
**reasoning is prose, not JSON.** Do not write field names, braces, or a response object inside it,
|
|
2608
2606
|
and never continue the response envelope in the reasoning text -- nextActions is a separate field
|
|
@@ -2613,14 +2611,16 @@ that you fill separately. A response carrying reasoning alone is discarded and r
|
|
|
2613
2611
|
${actionsList}
|
|
2614
2612
|
|
|
2615
2613
|
**Formats:**
|
|
2616
|
-
- tool-call: { "type": "tool-call", "id": "unique-id", "name": "tool_name", "input": {...} }${
|
|
2617
|
-
- message: { "type": "message", "text": "Your message" }` : ""}${includeNavigateKnowledge ? `
|
|
2614
|
+
- tool-call: { "type": "tool-call", "id": "unique-id", "name": "tool_name", "input": {...} }${includeNavigateKnowledge ? `
|
|
2618
2615
|
- navigate-knowledge: { "type": "navigate-knowledge", "id": "unique-id", "nodeId": "node-name" }` : ""}
|
|
2619
2616
|
- complete: { "type": "complete" }
|
|
2620
|
-
|
|
2617
|
+
${includeMessageAction ? `
|
|
2618
|
+
Talking to the user is NOT an action. There is no message action -- put your reply in the
|
|
2619
|
+
**message** field beside nextActions.
|
|
2620
|
+
` : ""}
|
|
2621
2621
|
## Execution Flow
|
|
2622
2622
|
|
|
2623
|
-
1. You respond with reasoning + actions
|
|
2623
|
+
1. You respond with reasoning + actions${includeMessageAction ? " + your message to the user" : ""}
|
|
2624
2624
|
2. System executes actions (tool calls run **in parallel**)
|
|
2625
2625
|
3. Tool results automatically appear in your next iteration
|
|
2626
2626
|
4. You see results and decide: more work needed? Or complete?
|
|
@@ -2632,10 +2632,10 @@ ${actionsList}
|
|
|
2632
2632
|
- Dependent operations need separate iterations (tool B needs tool A's result)
|
|
2633
2633
|
- "complete" cannot mix with navigate-knowledge${includeNavigateKnowledge ? "" : " (when available)"}
|
|
2634
2634
|
- "complete" can mix with tool-call when the tool is a fire-and-forget side effect and you do not need its result before ending${includeMessageAction ? `
|
|
2635
|
-
- Always
|
|
2636
|
-
-
|
|
2637
|
-
- When you have your answer,
|
|
2638
|
-
- Never repeat or rephrase the same answer across iterations. One clear answer, then complete
|
|
2635
|
+
- Always fill message before completing -- it is the only field the user sees, and completing with it empty ends the turn in silence
|
|
2636
|
+
- message holds one reply. Write the whole reply in it; do not split a reply across iterations
|
|
2637
|
+
- When you have your answer, put it in message and include complete in the SAME iteration. Never reply on one iteration then complete on a later one
|
|
2638
|
+
- Never repeat or rephrase the same answer across iterations. One clear answer, then complete` : ""}
|
|
2639
2639
|
|
|
2640
2640
|
**Use "complete" when:**
|
|
2641
2641
|
- Task finished successfully
|
|
@@ -2649,27 +2649,27 @@ ${actionsList}
|
|
|
2649
2649
|
|
|
2650
2650
|
## Examples
|
|
2651
2651
|
|
|
2652
|
-
Each example shows the
|
|
2652
|
+
Each example shows the field values, not a JSON document to copy.
|
|
2653
2653
|
|
|
2654
2654
|
### Example 1: Simple Task (No Tools)
|
|
2655
|
-
- reasoning: Simple greeting, no tools needed
|
|
2656
|
-
- nextActions: [
|
|
2655
|
+
- reasoning: Simple greeting, no tools needed.${includeMessageAction ? "\n- message: Hi! How can I help?" : ""}
|
|
2656
|
+
- nextActions: [{ "type": "complete" }]
|
|
2657
2657
|
|
|
2658
2658
|
### Example 2: Tool Usage (Two Iterations)
|
|
2659
2659
|
|
|
2660
2660
|
**Iteration 1 - Call tool (NO complete - waiting for results):**
|
|
2661
|
-
- reasoning: User asked for time. Calling get_time tool
|
|
2662
|
-
- nextActions: [
|
|
2661
|
+
- reasoning: User asked for time. Calling get_time tool.${includeMessageAction ? "\n- message: Checking the time..." : ""}
|
|
2662
|
+
- nextActions: [{ "type": "tool-call", "id": "t1", "name": "get_time", "input": { "timezone": "UTC" } }]
|
|
2663
2663
|
|
|
2664
2664
|
**Iteration 2 - Tool result received, now complete:**
|
|
2665
|
-
- reasoning: Got time result: 12:00 PM UTC. Task done.
|
|
2666
|
-
- nextActions: [
|
|
2665
|
+
- reasoning: Got time result: 12:00 PM UTC. Task done.${includeMessageAction ? "\n- message: The current time is 12:00 PM UTC." : ""}
|
|
2666
|
+
- nextActions: [{ "type": "complete" }]
|
|
2667
2667
|
|
|
2668
2668
|
### Example 3: Parallel Tool Calls (Independent Operations)
|
|
2669
2669
|
When tools don't depend on each other, batch them for faster execution.
|
|
2670
2670
|
|
|
2671
|
-
- reasoning: User wants time AND weather. Independent operations - calling both in parallel
|
|
2672
|
-
- nextActions: [
|
|
2671
|
+
- reasoning: User wants time AND weather. Independent operations - calling both in parallel.${includeMessageAction ? "\n- message: Getting time and weather..." : ""}
|
|
2672
|
+
- nextActions: [{ "type": "tool-call", "id": "t1", "name": "get_time", "input": {} }, { "type": "tool-call", "id": "w1", "name": "get_weather", "input": { "city": "NYC" } }]
|
|
2673
2673
|
|
|
2674
2674
|
### Example 4: Dependent Operations (Separate Iterations Required)
|
|
2675
2675
|
|
|
@@ -2679,8 +2679,8 @@ When tools don't depend on each other, batch them for faster execution.
|
|
|
2679
2679
|
Problem: update_user needs userId from search_user result!
|
|
2680
2680
|
|
|
2681
2681
|
**\u2705 CORRECT - Iteration 1 (get the dependency):**
|
|
2682
|
-
- reasoning: Need to find user first before updating
|
|
2683
|
-
- nextActions: [
|
|
2682
|
+
- reasoning: Need to find user first before updating.${includeMessageAction ? "\n- message: Looking up user..." : ""}
|
|
2683
|
+
- nextActions: [{ "type": "tool-call", "id": "1", "name": "search_user", "input": { "email": "user@example.com" } }]
|
|
2684
2684
|
|
|
2685
2685
|
**\u2705 CORRECT - Iteration 2 (use the result):**
|
|
2686
2686
|
- reasoning: Found userId: user_123. Now can update.
|
|
@@ -2763,7 +2763,7 @@ function buildToolsPrompt(tools) {
|
|
|
2763
2763
|
section += '{\n "type": "tool-call",\n "id": "unique-id",\n "name": "tool-name",\n "input": { /* tool input matching schema */ }\n}\n\n';
|
|
2764
2764
|
section += "**IMPORTANT RULES:**\n";
|
|
2765
2765
|
section += '1. "complete" CANNOT mix with navigate-knowledge actions in the same response\n';
|
|
2766
|
-
section += '2. "
|
|
2766
|
+
section += '2. The "message" field CAN be filled on the same response that completes - always pair your final message with complete in the same iteration\n';
|
|
2767
2767
|
section += '3. "complete" CAN mix with fire-and-forget tool-call actions when you do not need their results\n';
|
|
2768
2768
|
section += "4. To use tools and inspect their results, return ONLY tool-call actions, then wait for results in the next iteration\n";
|
|
2769
2769
|
section += "5. After receiving tool results, you can either call more tools OR complete with final answer\n";
|
|
@@ -3296,6 +3296,7 @@ var MemoryOperationsSchema = z.object({
|
|
|
3296
3296
|
});
|
|
3297
3297
|
var AgentIterationOutputSchema = z.object({
|
|
3298
3298
|
reasoning: z.string(),
|
|
3299
|
+
message: z.string().optional(),
|
|
3299
3300
|
memoryOps: MemoryOperationsSchema.optional(),
|
|
3300
3301
|
nextActions: z.array(AgentActionSchema)
|
|
3301
3302
|
});
|
|
@@ -3344,6 +3345,17 @@ ${memory.framing}` : memory.framing },
|
|
|
3344
3345
|
}
|
|
3345
3346
|
return messages;
|
|
3346
3347
|
}
|
|
3348
|
+
function withSynthesizedMessage(nextActions, message) {
|
|
3349
|
+
const text = message?.trim();
|
|
3350
|
+
if (!text) {
|
|
3351
|
+
return nextActions;
|
|
3352
|
+
}
|
|
3353
|
+
const alreadyPresent = nextActions.some((action) => action.type === "message" && action.text.trim() === text);
|
|
3354
|
+
if (alreadyPresent) {
|
|
3355
|
+
return nextActions;
|
|
3356
|
+
}
|
|
3357
|
+
return [{ type: "message", text }, ...nextActions];
|
|
3358
|
+
}
|
|
3347
3359
|
async function callLLMForAgentIteration(adapter, request) {
|
|
3348
3360
|
validateTokenConfiguration(request.model, request.constraints.maxOutputTokens);
|
|
3349
3361
|
const messages = buildAgentMessages(
|
|
@@ -3381,7 +3393,7 @@ async function callLLMForAgentIteration(adapter, request) {
|
|
|
3381
3393
|
return {
|
|
3382
3394
|
reasoning: validated.reasoning,
|
|
3383
3395
|
memoryOps: validated.memoryOps,
|
|
3384
|
-
nextActions: validated.nextActions
|
|
3396
|
+
nextActions: withSynthesizedMessage(validated.nextActions, validated.message)
|
|
3385
3397
|
};
|
|
3386
3398
|
} catch (error) {
|
|
3387
3399
|
flowLog("agent.iteration.validationFailed", {
|
|
@@ -3389,6 +3401,7 @@ async function callLLMForAgentIteration(adapter, request) {
|
|
|
3389
3401
|
missingRequired: ["reasoning", "nextActions"].filter(
|
|
3390
3402
|
(k) => !(typeof response.output === "object" && response.output !== null && k in response.output)
|
|
3391
3403
|
),
|
|
3404
|
+
messagePresent: typeof response.output === "object" && response.output !== null && typeof response.output.message === "string",
|
|
3392
3405
|
zodIssues: error instanceof ZodError ? error.issues.map((i) => ({ path: i.path.join("."), code: i.code })) : void 0
|
|
3393
3406
|
});
|
|
3394
3407
|
throw new AgentOutputValidationError("Agent iteration output validation failed", {
|
|
@@ -3466,17 +3479,6 @@ function buildIterationResponseSchema(tools, includeMessageAction, includeNaviga
|
|
|
3466
3479
|
required: ["type"],
|
|
3467
3480
|
additionalProperties: false
|
|
3468
3481
|
});
|
|
3469
|
-
if (includeMessageAction) {
|
|
3470
|
-
actionSchemas.push({
|
|
3471
|
-
type: "object",
|
|
3472
|
-
properties: {
|
|
3473
|
-
type: { type: "string", enum: ["message"] },
|
|
3474
|
-
text: { type: "string" }
|
|
3475
|
-
},
|
|
3476
|
-
required: ["type", "text"],
|
|
3477
|
-
additionalProperties: false
|
|
3478
|
-
});
|
|
3479
|
-
}
|
|
3480
3482
|
if (includeNavigateKnowledge) {
|
|
3481
3483
|
actionSchemas.push({
|
|
3482
3484
|
type: "object",
|
|
@@ -3495,9 +3497,15 @@ function buildIterationResponseSchema(tools, includeMessageAction, includeNaviga
|
|
|
3495
3497
|
items: {
|
|
3496
3498
|
anyOf: actionSchemas
|
|
3497
3499
|
}
|
|
3498
|
-
}
|
|
3499
|
-
reasoning: { type: "string", description: "Your reasoning process" }
|
|
3500
|
+
}
|
|
3500
3501
|
};
|
|
3502
|
+
if (includeMessageAction) {
|
|
3503
|
+
properties.message = {
|
|
3504
|
+
type: "string",
|
|
3505
|
+
description: "Your reply to the user, as plain prose. This is the ONLY field the user sees. Use an empty string only when this iteration just calls tools and you have nothing to say yet."
|
|
3506
|
+
};
|
|
3507
|
+
}
|
|
3508
|
+
properties.reasoning = { type: "string", description: "Your reasoning process" };
|
|
3501
3509
|
if (includeMemoryOps) {
|
|
3502
3510
|
properties.memoryOps = {
|
|
3503
3511
|
type: "object",
|
|
@@ -3528,7 +3536,7 @@ function buildIterationResponseSchema(tools, includeMessageAction, includeNaviga
|
|
|
3528
3536
|
return {
|
|
3529
3537
|
type: "object",
|
|
3530
3538
|
properties,
|
|
3531
|
-
required: ["nextActions", "reasoning"],
|
|
3539
|
+
required: includeMessageAction ? ["nextActions", "message", "reasoning"] : ["nextActions", "reasoning"],
|
|
3532
3540
|
additionalProperties: false
|
|
3533
3541
|
};
|
|
3534
3542
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elevasis/sdk",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.42.0",
|
|
4
4
|
"description": "SDK for building Elevasis organization resources",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -59,8 +59,8 @@
|
|
|
59
59
|
"typescript": "5.9.2",
|
|
60
60
|
"zod": "^4.1.0",
|
|
61
61
|
"@repo/core": "0.57.0",
|
|
62
|
-
"@repo/
|
|
63
|
-
"@repo/
|
|
62
|
+
"@repo/eslint-config": "0.0.0",
|
|
63
|
+
"@repo/typescript-config": "0.0.0"
|
|
64
64
|
},
|
|
65
65
|
"scripts": {
|
|
66
66
|
"lint": "eslint src --max-warnings 0",
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# The agent's reply to the user is now its own field
|
|
2
|
+
|
|
3
|
+
## Why this note exists
|
|
4
|
+
|
|
5
|
+
**Your agents' user-facing text was being silently corrupted, and no layer could catch it.**
|
|
6
|
+
|
|
7
|
+
Measured across three 14-turn production sessions on 2026-07-28: 42 of 42 turns passed, and **20
|
|
8
|
+
corruption sites landed across 12 of the 42 replies — 28.6%**. The model emits a JSON escape
|
|
9
|
+
sequence for an em-dash and then completes it with the next letter of the sentence, so
|
|
10
|
+
`Hold on — red-line for me` becomes `Hold on \red-line for me`. That parses as valid JSON, satisfies
|
|
11
|
+
the response schema, and passes validation. There is no error state, no retry, and no provider-side
|
|
12
|
+
fix to wait for. The characters are simply gone from the stored transcript.
|
|
13
|
+
|
|
14
|
+
The cause is where the text sat in the schema, not the text itself. The reply used to be one variant
|
|
15
|
+
of an `anyOf` union nested inside the `nextActions` array. In the very same responses, the flat
|
|
16
|
+
top-level `reasoning` string carried **33 em-dashes with zero corruption** while the nested reply
|
|
17
|
+
text carried **59 em-dashes with 20 corruption sites** — and `reasoning` is the longer field.
|
|
18
|
+
|
|
19
|
+
The reply is now a flat top-level `message` property, declared beside `reasoning` rather than nested
|
|
20
|
+
inside the actions array. Re-measured over a fresh multi-turn session: **42 em-dashes, zero
|
|
21
|
+
corruption.**
|
|
22
|
+
|
|
23
|
+
**This supersedes the two-field description in `2026-07-27-agent-strict-output-and-turn-drift.md`.**
|
|
24
|
+
That note described the iteration response as `nextActions` then `reasoning`. It is now three
|
|
25
|
+
fields, in this order: `nextActions`, `message`, `reasoning`. The ordering rationale from that note
|
|
26
|
+
is unchanged and still holds — `reasoning` stays last so the actions and the reply are already on the
|
|
27
|
+
wire before any mid-response drift can start.
|
|
28
|
+
|
|
29
|
+
**`message` is required for session-capable agents.** This is not a detail you can ignore. When the
|
|
30
|
+
field shipped as optional, the model filled it **zero times in three turns** and those turns produced
|
|
31
|
+
no user-facing message at all. Under grammar-constrained sampling the model fills keys in declaration
|
|
32
|
+
order, so `nextActions` is chosen before the model has reasoned about the reply, `complete` is the
|
|
33
|
+
cheapest legal action, and a skippable `message` then gets skipped. Making it required costs nothing:
|
|
34
|
+
an empty or whitespace-only value is suppressed and produces no message bubble.
|
|
35
|
+
|
|
36
|
+
## Applies to
|
|
37
|
+
|
|
38
|
+
- **Every `sessionCapable: true` agent.** `message` is the only field the user sees, so this is the
|
|
39
|
+
field that was being damaged.
|
|
40
|
+
- **Agents on Anthropic models**, where the enforced-schema path is active. On the unenforced path
|
|
41
|
+
the platform still accepts a message delivered the old way, as an entry in the actions list, so
|
|
42
|
+
nothing breaks mid-transition.
|
|
43
|
+
- **Anything that reads stored assistant messages** — a transcript, an export, a summarizer, a
|
|
44
|
+
downstream workflow. Already-stored text is not repaired by this change; see below.
|
|
45
|
+
- **No agent definition changes are required.** You do not edit your agents. The shape lives in the
|
|
46
|
+
runtime your bundle carries.
|
|
47
|
+
|
|
48
|
+
## Required actions
|
|
49
|
+
|
|
50
|
+
1. **Take the `@elevasis/sdk` baseline bump** this train propagates, then reinstall in `operations/`
|
|
51
|
+
so the new worker bundle is present.
|
|
52
|
+
2. **Redeploy your operations bundle.** This is the step that actually closes the defect. The
|
|
53
|
+
response schema is emitted by the runtime inlined into your deployed bundle, so an existing
|
|
54
|
+
deployment keeps emitting the old nested shape — and keeps corrupting replies — until it is
|
|
55
|
+
redeployed:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
pnpm -C operations exec elevasis-sdk deploy --prod
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
A platform-side deploy does not fix this for you, and neither does the reinstall on its own.
|
|
62
|
+
|
|
63
|
+
3. **If you read or display stored assistant text, do not treat old records as clean.** Nothing is
|
|
64
|
+
rewritten retroactively. Transcripts written before your redeploy keep whatever corruption they
|
|
65
|
+
already have.
|
|
66
|
+
|
|
67
|
+
## Verification
|
|
68
|
+
|
|
69
|
+
- Run a session and prompt for a reply likely to contain an em-dash — asking the agent to summarize
|
|
70
|
+
something in a couple of sentences is usually enough. Confirm the reply arrives, and read it: the
|
|
71
|
+
signature failure is a missing letter immediately after where punctuation belonged
|
|
72
|
+
(`Hold on \red-line`), not a visible error.
|
|
73
|
+
- **Confirm every turn produced an assistant message at all.** A turn that completes with no message
|
|
74
|
+
is the symptom of a bundle that has the flat field but not the required flag — that combination
|
|
75
|
+
only exists in an unreleased build, but it is the one failure worth ruling out explicitly.
|
|
76
|
+
- Check the observability rows for an execution: iteration calls should still record the enforced
|
|
77
|
+
path with no fallback reasons. This change does not push the schema off it.
|
|
78
|
+
|
|
79
|
+
## Not handled by /git-sync
|
|
80
|
+
|
|
81
|
+
- **The redeploy.** `/git-sync` commits and pushes the propagated dependency baseline. The corruption
|
|
82
|
+
continues on your deployed agents until you run action 2 above.
|
|
83
|
+
- **Repairing existing transcripts.** No backfill is performed, and none is possible — the dropped
|
|
84
|
+
characters were never received.
|