@kooka/agent-sdk 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -10
- package/dist/agent/agent.d.ts +7 -1
- package/dist/agent/agent.d.ts.map +1 -1
- package/dist/agent/agent.js +257 -19
- package/dist/agent/agent.js.map +1 -1
- package/dist/agent/prompts.d.ts +1 -1
- package/dist/agent/prompts.d.ts.map +1 -1
- package/dist/agent/prompts.js +2 -0
- package/dist/agent/prompts.js.map +1 -1
- package/dist/agent/reminders.d.ts +5 -1
- package/dist/agent/reminders.d.ts.map +1 -1
- package/dist/agent/reminders.js +14 -3
- package/dist/agent/reminders.js.map +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/persistence/sessionSnapshot.d.ts +8 -0
- package/dist/persistence/sessionSnapshot.d.ts.map +1 -1
- package/dist/persistence/sessionSnapshot.js +16 -0
- package/dist/persistence/sessionSnapshot.js.map +1 -1
- package/dist/plugins/pluginManager.d.ts.map +1 -1
- package/dist/plugins/pluginManager.js +19 -3
- package/dist/plugins/pluginManager.js.map +1 -1
- package/dist/tools/agentBrowser.js +15 -15
- package/dist/tools/agentBrowser.js.map +1 -1
- package/dist/tools/builtin/bash.d.ts.map +1 -1
- package/dist/tools/builtin/bash.js +3 -2
- package/dist/tools/builtin/bash.js.map +1 -1
- package/dist/tools/builtin/index.d.ts.map +1 -1
- package/dist/tools/builtin/index.js +2 -0
- package/dist/tools/builtin/index.js.map +1 -1
- package/dist/tools/builtin/task.d.ts +4 -0
- package/dist/tools/builtin/task.d.ts.map +1 -0
- package/dist/tools/builtin/task.js +47 -0
- package/dist/tools/builtin/task.js.map +1 -0
- package/dist/tools/builtin/workspace.d.ts.map +1 -1
- package/dist/tools/builtin/workspace.js +42 -3
- package/dist/tools/builtin/workspace.js.map +1 -1
- package/dist/tools/registry.d.ts.map +1 -1
- package/dist/tools/registry.js +19 -1
- package/dist/tools/registry.js.map +1 -1
- package/dist/types.d.ts +5 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -111,11 +111,11 @@ import { createLingyunAgent, type ToolDefinition } from '@kooka/agent-sdk';
|
|
|
111
111
|
const { agent, registry } = createLingyunAgent({ /* ... */ });
|
|
112
112
|
|
|
113
113
|
const timeTool: ToolDefinition = {
|
|
114
|
-
id: '
|
|
115
|
-
name: '
|
|
114
|
+
id: 'time_now',
|
|
115
|
+
name: 'time_now',
|
|
116
116
|
description: 'Get the current time as an ISO string.',
|
|
117
117
|
parameters: { type: 'object', properties: {} },
|
|
118
|
-
execution: { type: 'function', handler: '
|
|
118
|
+
execution: { type: 'function', handler: 'time_now' },
|
|
119
119
|
metadata: { readOnly: true },
|
|
120
120
|
};
|
|
121
121
|
|
|
@@ -162,7 +162,7 @@ Useful event types:
|
|
|
162
162
|
|
|
163
163
|
- `assistant_token`: user-facing assistant text (with `<think>` and tool-call markers removed)
|
|
164
164
|
- `thought_token`: model “thinking” tokens (only if the provider emits them)
|
|
165
|
-
- `notice`: user-facing notices from the runtime (e.g.
|
|
165
|
+
- `notice`: user-facing notices from the runtime (e.g. subagent model fallback warnings)
|
|
166
166
|
- `tool_call` / `tool_result` / `tool_blocked`: tool lifecycle
|
|
167
167
|
- `compaction_start` / `compaction_end`: context overflow mitigation
|
|
168
168
|
|
|
@@ -188,7 +188,7 @@ If a user message includes `$<skill-name>`, LingYun:
|
|
|
188
188
|
1. Looks up the skill by `name:` in discovered `SKILL.md` files
|
|
189
189
|
2. Injects the skill body as a synthetic `<skill>...</skill>` user message before calling the model
|
|
190
190
|
|
|
191
|
-
Unknown skills are ignored
|
|
191
|
+
Unknown skills are ignored.
|
|
192
192
|
|
|
193
193
|
Configure discovery/injection via `tools.builtinOptions.skills`:
|
|
194
194
|
|
|
@@ -233,7 +233,7 @@ const { registry } = createLingyunAgent({
|
|
|
233
233
|
|
|
234
234
|
registry.registerTool(
|
|
235
235
|
{
|
|
236
|
-
id: '
|
|
236
|
+
id: 'demo_echo',
|
|
237
237
|
name: 'Echo',
|
|
238
238
|
description: 'Echo back the message argument',
|
|
239
239
|
parameters: {
|
|
@@ -241,7 +241,7 @@ registry.registerTool(
|
|
|
241
241
|
properties: { message: { type: 'string' } },
|
|
242
242
|
required: ['message'],
|
|
243
243
|
},
|
|
244
|
-
execution: { type: 'function', handler: '
|
|
244
|
+
execution: { type: 'function', handler: 'demo_echo' },
|
|
245
245
|
metadata: { requiresApproval: false, permission: 'read', readOnly: true },
|
|
246
246
|
},
|
|
247
247
|
async (args) => ({ success: true, data: `Echo: ${String(args.message ?? '')}` })
|
|
@@ -276,9 +276,9 @@ registerAgentBrowserTools(registry, {
|
|
|
276
276
|
```
|
|
277
277
|
|
|
278
278
|
Tools:
|
|
279
|
-
- `
|
|
280
|
-
- `
|
|
281
|
-
- `
|
|
279
|
+
- `browser_start_session` / `browser_close_session`
|
|
280
|
+
- `browser_snapshot` (read-only; returns accessibility tree with refs like `@e2`)
|
|
281
|
+
- `browser_run` (requires approval; runs click/fill/type/wait/get/screenshot/pdf/trace actions)
|
|
282
282
|
|
|
283
283
|
Security defaults:
|
|
284
284
|
- HTTPS-only and blocks private hosts / IPs by default
|
package/dist/agent/agent.d.ts
CHANGED
|
@@ -6,11 +6,15 @@ export declare class LingyunSession {
|
|
|
6
6
|
history: AgentHistoryMessage[];
|
|
7
7
|
pendingPlan?: string;
|
|
8
8
|
sessionId?: string;
|
|
9
|
+
parentSessionId?: string;
|
|
10
|
+
subagentType?: string;
|
|
11
|
+
modelId?: string;
|
|
12
|
+
mentionedSkills: string[];
|
|
9
13
|
fileHandles?: {
|
|
10
14
|
nextId: number;
|
|
11
15
|
byId: Record<string, string>;
|
|
12
16
|
};
|
|
13
|
-
constructor(init?: Partial<Pick<LingyunSession, 'history' | 'pendingPlan' | 'sessionId' | 'fileHandles'>>);
|
|
17
|
+
constructor(init?: Partial<Pick<LingyunSession, 'history' | 'pendingPlan' | 'sessionId' | 'parentSessionId' | 'subagentType' | 'modelId' | 'mentionedSkills' | 'fileHandles'>>);
|
|
14
18
|
getHistory(): AgentHistoryMessage[];
|
|
15
19
|
}
|
|
16
20
|
export type LingyunAgentRuntimeOptions = {
|
|
@@ -39,6 +43,8 @@ export declare class LingyunAgent {
|
|
|
39
43
|
private readonly modelLimits?;
|
|
40
44
|
private readonly compactionConfig;
|
|
41
45
|
private registeredPluginTools;
|
|
46
|
+
private readonly taskSessions;
|
|
47
|
+
private readonly maxTaskSessions;
|
|
42
48
|
constructor(llm: LLMProvider, config: AgentConfig, registry: ToolRegistry, runtime?: LingyunAgentRuntimeOptions);
|
|
43
49
|
updateConfig(config: Partial<AgentConfig>): void;
|
|
44
50
|
setAllowExternalPaths(allow: boolean): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../src/agent/agent.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAwC,cAAc,EAAE,WAAW,EAAE,WAAW,EAAgB,UAAU,EAAE,MAAM,aAAa,CAAC;AAE5I,OAAO,
|
|
1
|
+
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../src/agent/agent.ts"],"names":[],"mappings":"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"}
|
package/dist/agent/agent.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as path from 'path';
|
|
2
2
|
import { convertToModelMessages, extractReasoningMiddleware, jsonSchema, streamText, tool as aiTool, wrapLanguageModel, } from 'ai';
|
|
3
3
|
import { combineAbortSignals } from '../abort.js';
|
|
4
|
-
import { COMPACTION_AUTO_CONTINUE_TEXT, COMPACTION_MARKER_TEXT, COMPACTION_PROMPT_TEXT, COMPACTION_SYSTEM_PROMPT, createAssistantHistoryMessage, createHistoryForCompactionPrompt, createHistoryForModel, createUserHistoryMessage, evaluatePermission, evaluateShellCommand, extractSkillMentions, extractUsageTokens, finalizeStreamingParts, findExternalPathReferencesInShellCommand, getEffectiveHistory, getMessageText, getReservedOutputTokens, isOverflow as isContextOverflow, isPathInsideWorkspace, markPreviousAssistantToolOutputs, redactFsPathForPrompt, renderSkillsSectionForPrompt, selectSkillsForText, setDynamicToolError, setDynamicToolOutput, upsertDynamicToolCall, } from '@kooka/core';
|
|
4
|
+
import { COMPACTION_AUTO_CONTINUE_TEXT, COMPACTION_MARKER_TEXT, COMPACTION_PROMPT_TEXT, COMPACTION_SYSTEM_PROMPT, createAssistantHistoryMessage, applyAssistantReplayForPrompt, applyCopilotImageInputPattern, applyOpenAICompatibleReasoningField, createHistoryForCompactionPrompt, createHistoryForModel, createUserHistoryMessage, evaluatePermission, evaluateShellCommand, extractSkillMentions, extractUsageTokens, finalizeStreamingParts, findExternalPathReferencesInShellCommand, getEffectiveHistory, getMessageText, getReservedOutputTokens, isOverflow as isContextOverflow, isPathInsideWorkspace, listBuiltinSubagents, normalizeSessionId, markPreviousAssistantToolOutputs, redactFsPathForPrompt, renderSkillsSectionForPrompt, requireString, resolveBuiltinSubagent, selectSkillsForText, setDynamicToolError, setDynamicToolOutput, upsertDynamicToolCall, } from '@kooka/core';
|
|
5
5
|
import { PluginManager } from '../plugins/pluginManager.js';
|
|
6
6
|
import { insertModeReminders } from './reminders.js';
|
|
7
7
|
import { DEFAULT_SYSTEM_PROMPT } from './prompts.js';
|
|
@@ -107,6 +107,10 @@ export class LingyunSession {
|
|
|
107
107
|
history = [];
|
|
108
108
|
pendingPlan;
|
|
109
109
|
sessionId;
|
|
110
|
+
parentSessionId;
|
|
111
|
+
subagentType;
|
|
112
|
+
modelId;
|
|
113
|
+
mentionedSkills = [];
|
|
110
114
|
fileHandles;
|
|
111
115
|
constructor(init) {
|
|
112
116
|
if (init?.history)
|
|
@@ -115,6 +119,14 @@ export class LingyunSession {
|
|
|
115
119
|
this.pendingPlan = init.pendingPlan;
|
|
116
120
|
if (init?.sessionId)
|
|
117
121
|
this.sessionId = init.sessionId;
|
|
122
|
+
if (init?.parentSessionId)
|
|
123
|
+
this.parentSessionId = init.parentSessionId;
|
|
124
|
+
if (init?.subagentType)
|
|
125
|
+
this.subagentType = init.subagentType;
|
|
126
|
+
if (init?.modelId)
|
|
127
|
+
this.modelId = init.modelId;
|
|
128
|
+
if (init?.mentionedSkills)
|
|
129
|
+
this.mentionedSkills = [...init.mentionedSkills];
|
|
118
130
|
if (init?.fileHandles)
|
|
119
131
|
this.fileHandles = init.fileHandles;
|
|
120
132
|
}
|
|
@@ -134,6 +146,8 @@ export class LingyunAgent {
|
|
|
134
146
|
modelLimits;
|
|
135
147
|
compactionConfig;
|
|
136
148
|
registeredPluginTools = new Set();
|
|
149
|
+
taskSessions = new Map();
|
|
150
|
+
maxTaskSessions = 50;
|
|
137
151
|
constructor(llm, config, registry, runtime) {
|
|
138
152
|
this.llm = llm;
|
|
139
153
|
this.config = config;
|
|
@@ -335,11 +349,16 @@ export class LingyunAgent {
|
|
|
335
349
|
async toModelMessages(session, tools, modelId) {
|
|
336
350
|
const effective = getEffectiveHistory(session.history);
|
|
337
351
|
const prepared = createHistoryForModel(effective);
|
|
338
|
-
const reminded = insertModeReminders(prepared, this.getMode());
|
|
352
|
+
const reminded = insertModeReminders(prepared, this.getMode(), { allowExternalPaths: this.allowExternalPaths });
|
|
339
353
|
const withoutIds = reminded.map(({ id: _id, ...rest }) => rest);
|
|
340
354
|
const messagesOutput = await this.plugins.trigger('experimental.chat.messages.transform', { sessionId: session.sessionId ?? this.config.sessionId, mode: this.getMode(), modelId }, { messages: [...withoutIds] });
|
|
341
355
|
const messages = Array.isArray(messagesOutput.messages) ? messagesOutput.messages : withoutIds;
|
|
342
|
-
|
|
356
|
+
const replayed = this.llm.id === 'openaiCompatible'
|
|
357
|
+
? applyAssistantReplayForPrompt(messages)
|
|
358
|
+
: messages;
|
|
359
|
+
const converted = await convertToModelMessages(replayed, { tools: tools });
|
|
360
|
+
const withReasoning = this.llm.id === 'openaiCompatible' ? applyOpenAICompatibleReasoningField(converted) : converted;
|
|
361
|
+
return this.llm.id === 'copilot' ? applyCopilotImageInputPattern(withReasoning) : withReasoning;
|
|
343
362
|
}
|
|
344
363
|
createToolContext(signal, session, callbacks) {
|
|
345
364
|
return {
|
|
@@ -504,6 +523,213 @@ export class LingyunAgent {
|
|
|
504
523
|
for (const def of tools) {
|
|
505
524
|
const toolName = def.id;
|
|
506
525
|
toolNameToDefinition.set(toolName, def);
|
|
526
|
+
if (toolName === 'task') {
|
|
527
|
+
out[toolName] = aiTool({
|
|
528
|
+
id: toolName,
|
|
529
|
+
description: def.description,
|
|
530
|
+
inputSchema: jsonSchema(def.parameters),
|
|
531
|
+
execute: async (args, options) => {
|
|
532
|
+
const resolvedArgs = args ?? {};
|
|
533
|
+
if (session.parentSessionId || session.subagentType) {
|
|
534
|
+
return {
|
|
535
|
+
success: false,
|
|
536
|
+
error: 'Subagents cannot spawn other subagents via task.',
|
|
537
|
+
metadata: { errorType: 'task_recursion_denied' },
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
const parentMode = this.getMode();
|
|
541
|
+
const descriptionResult = requireString(resolvedArgs, 'description');
|
|
542
|
+
if ('error' in descriptionResult)
|
|
543
|
+
return { success: false, error: descriptionResult.error };
|
|
544
|
+
const promptResult = requireString(resolvedArgs, 'prompt');
|
|
545
|
+
if ('error' in promptResult)
|
|
546
|
+
return { success: false, error: promptResult.error };
|
|
547
|
+
const typeResult = requireString(resolvedArgs, 'subagent_type');
|
|
548
|
+
if ('error' in typeResult)
|
|
549
|
+
return { success: false, error: typeResult.error };
|
|
550
|
+
const subagentTypeRaw = typeResult.value.trim();
|
|
551
|
+
const subagent = resolveBuiltinSubagent(subagentTypeRaw);
|
|
552
|
+
if (!subagent) {
|
|
553
|
+
const names = listBuiltinSubagents().map((a) => a.name).join(', ');
|
|
554
|
+
return {
|
|
555
|
+
success: false,
|
|
556
|
+
error: `Unknown subagent_type: ${subagentTypeRaw}. Available: ${names || '(none)'}`,
|
|
557
|
+
metadata: { errorType: 'unknown_subagent_type', subagentType: subagentTypeRaw },
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
if (parentMode === 'plan' && subagent.name !== 'explore') {
|
|
561
|
+
return {
|
|
562
|
+
success: false,
|
|
563
|
+
error: 'Only the explore subagent is allowed in Plan mode.',
|
|
564
|
+
metadata: { errorType: 'subagent_denied_in_plan', subagentType: subagent.name },
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
const sessionIdRaw = typeof resolvedArgs.session_id === 'string' && resolvedArgs.session_id.trim()
|
|
568
|
+
? String(resolvedArgs.session_id).trim()
|
|
569
|
+
: '';
|
|
570
|
+
const parentSessionId = session.sessionId ?? this.config.sessionId;
|
|
571
|
+
const requestedSessionId = normalizeSessionId(sessionIdRaw) || '';
|
|
572
|
+
const childSessionId = requestedSessionId || crypto.randomUUID();
|
|
573
|
+
const existing = this.taskSessions.get(childSessionId);
|
|
574
|
+
const childSession = existing ??
|
|
575
|
+
new LingyunSession({
|
|
576
|
+
sessionId: childSessionId,
|
|
577
|
+
parentSessionId,
|
|
578
|
+
subagentType: subagent.name,
|
|
579
|
+
});
|
|
580
|
+
if (!existing) {
|
|
581
|
+
this.taskSessions.set(childSessionId, childSession);
|
|
582
|
+
while (this.taskSessions.size > this.maxTaskSessions) {
|
|
583
|
+
const oldestKey = this.taskSessions.keys().next().value;
|
|
584
|
+
if (!oldestKey)
|
|
585
|
+
break;
|
|
586
|
+
if (oldestKey === childSessionId)
|
|
587
|
+
break;
|
|
588
|
+
this.taskSessions.delete(oldestKey);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
else {
|
|
592
|
+
childSession.parentSessionId = parentSessionId;
|
|
593
|
+
childSession.subagentType = subagent.name;
|
|
594
|
+
// Refresh LRU order.
|
|
595
|
+
this.taskSessions.delete(childSessionId);
|
|
596
|
+
this.taskSessions.set(childSessionId, childSession);
|
|
597
|
+
}
|
|
598
|
+
const parentModelId = this.config.model;
|
|
599
|
+
if (!parentModelId) {
|
|
600
|
+
return {
|
|
601
|
+
success: false,
|
|
602
|
+
error: 'No model configured. Set AgentConfig.model.',
|
|
603
|
+
metadata: { errorType: 'missing_model' },
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
const configuredSubagentModel = typeof this.config.subagentModel === 'string' ? this.config.subagentModel.trim() : '';
|
|
607
|
+
const desiredChildModelId = childSession.modelId || configuredSubagentModel || parentModelId;
|
|
608
|
+
let childModelId = parentModelId;
|
|
609
|
+
let childModelWarning;
|
|
610
|
+
if (desiredChildModelId !== parentModelId) {
|
|
611
|
+
try {
|
|
612
|
+
await this.llm.getModel(desiredChildModelId);
|
|
613
|
+
childModelId = desiredChildModelId;
|
|
614
|
+
}
|
|
615
|
+
catch (error) {
|
|
616
|
+
childModelWarning =
|
|
617
|
+
`Subagent model "${desiredChildModelId}" is unavailable; ` +
|
|
618
|
+
`using parent model "${parentModelId}".`;
|
|
619
|
+
callbacks?.onNotice?.({ level: 'warning', message: childModelWarning });
|
|
620
|
+
callbacks?.onDebug?.(`[Task] subagent model fallback requested=${desiredChildModelId} using=${parentModelId} error=${error instanceof Error ? error.message : String(error)}`);
|
|
621
|
+
childModelId = parentModelId;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
childSession.modelId = childModelId;
|
|
625
|
+
const basePrompt = this.config.systemPrompt || DEFAULT_SYSTEM_PROMPT;
|
|
626
|
+
const subagentConfig = {
|
|
627
|
+
model: childModelId,
|
|
628
|
+
mode: 'build',
|
|
629
|
+
temperature: this.config.temperature,
|
|
630
|
+
maxRetries: this.config.maxRetries,
|
|
631
|
+
maxOutputTokens: this.config.maxOutputTokens,
|
|
632
|
+
autoApprove: this.config.autoApprove,
|
|
633
|
+
toolFilter: subagent.toolFilter?.length ? subagent.toolFilter : undefined,
|
|
634
|
+
systemPrompt: `${basePrompt}\n\n${subagent.prompt}`,
|
|
635
|
+
sessionId: childSessionId,
|
|
636
|
+
};
|
|
637
|
+
const subagentRunner = new LingyunAgent(this.llm, subagentConfig, this.registry, {
|
|
638
|
+
plugins: this.plugins,
|
|
639
|
+
workspaceRoot: this.workspaceRoot,
|
|
640
|
+
allowExternalPaths: this.allowExternalPaths,
|
|
641
|
+
skills: this.skillsConfig,
|
|
642
|
+
modelLimits: this.modelLimits,
|
|
643
|
+
compaction: this.compactionConfig,
|
|
644
|
+
});
|
|
645
|
+
const toolSummary = new Map();
|
|
646
|
+
const childCallbacks = {
|
|
647
|
+
onRequestApproval: callbacks?.onRequestApproval,
|
|
648
|
+
onToolCall: (tool, definition) => {
|
|
649
|
+
toolSummary.set(tool.id, { id: tool.id, tool: definition.id, status: 'running' });
|
|
650
|
+
},
|
|
651
|
+
onToolResult: (tool, result) => {
|
|
652
|
+
const prev = toolSummary.get(tool.id);
|
|
653
|
+
const nextStatus = result.success ? 'success' : 'error';
|
|
654
|
+
toolSummary.set(tool.id, {
|
|
655
|
+
id: tool.id,
|
|
656
|
+
tool: prev?.tool ?? tool.function.name,
|
|
657
|
+
status: nextStatus,
|
|
658
|
+
});
|
|
659
|
+
},
|
|
660
|
+
};
|
|
661
|
+
try {
|
|
662
|
+
const run = subagentRunner.run({
|
|
663
|
+
session: childSession,
|
|
664
|
+
input: promptResult.value,
|
|
665
|
+
callbacks: childCallbacks,
|
|
666
|
+
signal: options.abortSignal,
|
|
667
|
+
});
|
|
668
|
+
const drain = (async () => {
|
|
669
|
+
for await (const _event of run.events) {
|
|
670
|
+
// drain
|
|
671
|
+
}
|
|
672
|
+
})();
|
|
673
|
+
const done = await run.done;
|
|
674
|
+
await drain;
|
|
675
|
+
const text = done.text || '';
|
|
676
|
+
const outputText = text.trimEnd() +
|
|
677
|
+
'\n\n' +
|
|
678
|
+
['<task_metadata>', `session_id: ${childSessionId}`, '</task_metadata>'].join('\n');
|
|
679
|
+
const summary = [...toolSummary.values()].sort((a, b) => a.id.localeCompare(b.id));
|
|
680
|
+
return {
|
|
681
|
+
success: true,
|
|
682
|
+
data: {
|
|
683
|
+
session_id: childSessionId,
|
|
684
|
+
subagent_type: subagent.name,
|
|
685
|
+
text,
|
|
686
|
+
},
|
|
687
|
+
metadata: {
|
|
688
|
+
title: descriptionResult.value,
|
|
689
|
+
outputText,
|
|
690
|
+
task: {
|
|
691
|
+
description: descriptionResult.value,
|
|
692
|
+
subagent_type: subagent.name,
|
|
693
|
+
session_id: childSessionId,
|
|
694
|
+
parent_session_id: parentSessionId,
|
|
695
|
+
summary,
|
|
696
|
+
model_id: childModelId,
|
|
697
|
+
...(childModelWarning
|
|
698
|
+
? { model_warning: childModelWarning, requested_model_id: desiredChildModelId }
|
|
699
|
+
: {}),
|
|
700
|
+
},
|
|
701
|
+
childSession: {
|
|
702
|
+
sessionId: childSessionId,
|
|
703
|
+
parentSessionId,
|
|
704
|
+
subagentType: subagent.name,
|
|
705
|
+
modelId: childModelId,
|
|
706
|
+
history: childSession.getHistory(),
|
|
707
|
+
pendingPlan: childSession.pendingPlan,
|
|
708
|
+
fileHandles: childSession.fileHandles,
|
|
709
|
+
},
|
|
710
|
+
},
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
catch (error) {
|
|
714
|
+
return {
|
|
715
|
+
success: false,
|
|
716
|
+
error: error instanceof Error ? error.message : String(error),
|
|
717
|
+
metadata: { errorType: 'task_subagent_failed' },
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
finally {
|
|
721
|
+
// Avoid leaking subagent's temporary skill injection messages to later turns.
|
|
722
|
+
childSession.history = childSession.history.filter((msg) => !(msg.role === 'user' && msg.metadata?.skill));
|
|
723
|
+
}
|
|
724
|
+
},
|
|
725
|
+
toModelOutput: async (options) => {
|
|
726
|
+
const output = options.output;
|
|
727
|
+
const content = await this.formatToolResult(output, def.name);
|
|
728
|
+
return { type: 'text', value: content };
|
|
729
|
+
},
|
|
730
|
+
});
|
|
731
|
+
continue;
|
|
732
|
+
}
|
|
507
733
|
out[toolName] = aiTool({
|
|
508
734
|
id: toolName,
|
|
509
735
|
description: def.description,
|
|
@@ -768,7 +994,10 @@ export class LingyunAgent {
|
|
|
768
994
|
const prepared = createHistoryForCompactionPrompt(effective, this.compactionConfig);
|
|
769
995
|
const withoutIds = prepared.map(({ id: _id, ...rest }) => rest);
|
|
770
996
|
const compactionUser = createUserHistoryMessage(promptText, { synthetic: true });
|
|
771
|
-
const
|
|
997
|
+
const convertedCompactionModelMessages = await convertToModelMessages([...withoutIds, compactionUser], { tools: {} });
|
|
998
|
+
const compactionModelMessages = this.llm.id === 'copilot'
|
|
999
|
+
? applyCopilotImageInputPattern(convertedCompactionModelMessages)
|
|
1000
|
+
: convertedCompactionModelMessages;
|
|
772
1001
|
const stream = streamText({
|
|
773
1002
|
model: compactionModel,
|
|
774
1003
|
system: COMPACTION_SYSTEM_PROMPT,
|
|
@@ -929,7 +1158,16 @@ export class LingyunAgent {
|
|
|
929
1158
|
const toolCallId = String(part.toolCallId);
|
|
930
1159
|
const def = toolNameToDefinition.get(toolName);
|
|
931
1160
|
const toolLabel = def?.name || toolName;
|
|
932
|
-
const
|
|
1161
|
+
const rawOutput = part.output;
|
|
1162
|
+
let output = await this.pruneToolResultForHistory(rawOutput, toolLabel);
|
|
1163
|
+
const isTaskTool = def?.id === 'task' || toolName === 'task';
|
|
1164
|
+
if (isTaskTool && output.metadata && typeof output.metadata === 'object') {
|
|
1165
|
+
// Do not persist child session snapshots inside the parent session history.
|
|
1166
|
+
const meta = { ...output.metadata };
|
|
1167
|
+
delete meta.childSession;
|
|
1168
|
+
delete meta.task;
|
|
1169
|
+
output = { ...output, metadata: meta };
|
|
1170
|
+
}
|
|
933
1171
|
setDynamicToolOutput(assistantMessage, {
|
|
934
1172
|
toolName,
|
|
935
1173
|
toolCallId,
|
|
@@ -937,7 +1175,12 @@ export class LingyunAgent {
|
|
|
937
1175
|
output,
|
|
938
1176
|
});
|
|
939
1177
|
const tc = toToolCall(toolCallId, toolName, part.input);
|
|
940
|
-
|
|
1178
|
+
if (isTaskTool && rawOutput && typeof rawOutput === 'object' && typeof rawOutput.success === 'boolean') {
|
|
1179
|
+
callbacksSafe?.onToolResult?.(tc, rawOutput);
|
|
1180
|
+
}
|
|
1181
|
+
else {
|
|
1182
|
+
callbacksSafe?.onToolResult?.(tc, output);
|
|
1183
|
+
}
|
|
941
1184
|
callbacksSafe?.onStatusChange?.({ type: 'running', message: '' });
|
|
942
1185
|
break;
|
|
943
1186
|
}
|
|
@@ -992,6 +1235,7 @@ export class LingyunAgent {
|
|
|
992
1235
|
assistantMessage.metadata = {
|
|
993
1236
|
mode: this.getMode(),
|
|
994
1237
|
finishReason: streamFinishReason,
|
|
1238
|
+
replay: { text: attemptText, reasoning: attemptReasoning },
|
|
995
1239
|
...(tokens ? { tokens } : {}),
|
|
996
1240
|
};
|
|
997
1241
|
const cleanedText = stripToolBlocks(stripThinkBlocks(attemptText)).trim();
|
|
@@ -1004,6 +1248,9 @@ export class LingyunAgent {
|
|
|
1004
1248
|
if (finalText) {
|
|
1005
1249
|
assistantMessage.parts.unshift({ type: 'text', text: finalText, state: 'streaming' });
|
|
1006
1250
|
}
|
|
1251
|
+
if (attemptReasoning.trim()) {
|
|
1252
|
+
assistantMessage.parts.unshift({ type: 'reasoning', text: attemptReasoning, state: 'streaming' });
|
|
1253
|
+
}
|
|
1007
1254
|
finalizeStreamingParts(assistantMessage);
|
|
1008
1255
|
session.history.push(assistantMessage);
|
|
1009
1256
|
const lastAssistantText = getMessageText(assistantMessage).trim();
|
|
@@ -1048,19 +1295,7 @@ export class LingyunAgent {
|
|
|
1048
1295
|
allowExternalPaths: this.allowExternalPaths,
|
|
1049
1296
|
signal,
|
|
1050
1297
|
});
|
|
1051
|
-
const { selected
|
|
1052
|
-
if (unknown.length > 0) {
|
|
1053
|
-
const availableSample = index.skills
|
|
1054
|
-
.map((s) => s.name)
|
|
1055
|
-
.slice(0, 20);
|
|
1056
|
-
const availableLabel = availableSample.length > 0
|
|
1057
|
-
? ` Available: ${availableSample.map((n) => `$${n}`).join(', ')}${index.skills.length > availableSample.length ? ', ...' : ''}`
|
|
1058
|
-
: '';
|
|
1059
|
-
callbacks?.onNotice?.({
|
|
1060
|
-
level: 'warning',
|
|
1061
|
-
message: `Unknown skills: ${unknown.map((n) => `$${n}`).join(', ')}.${availableLabel}`,
|
|
1062
|
-
});
|
|
1063
|
-
}
|
|
1298
|
+
const { selected } = selectSkillsForText(text, index);
|
|
1064
1299
|
if (selected.length === 0)
|
|
1065
1300
|
return;
|
|
1066
1301
|
const maxSkills = this.skillsConfig.maxInjectSkills;
|
|
@@ -1080,6 +1315,9 @@ export class LingyunAgent {
|
|
|
1080
1315
|
for (const skill of selectedForInject) {
|
|
1081
1316
|
if (signal?.aborted)
|
|
1082
1317
|
break;
|
|
1318
|
+
if (!session.mentionedSkills.includes(skill.name)) {
|
|
1319
|
+
session.mentionedSkills.push(skill.name);
|
|
1320
|
+
}
|
|
1083
1321
|
let body;
|
|
1084
1322
|
try {
|
|
1085
1323
|
body = (await loadSkillFile(skill)).content;
|