@kooka/agent-sdk 0.1.0 → 0.1.3
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 +143 -2
- package/dist/agent/agent.d.ts +11 -0
- package/dist/agent/agent.d.ts.map +1 -1
- package/dist/agent/agent.js +132 -7
- package/dist/agent/agent.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -1
- package/dist/index.js.map +1 -1
- package/dist/persistence/index.d.ts +5 -0
- package/dist/persistence/index.d.ts.map +1 -0
- package/dist/persistence/index.js +3 -0
- package/dist/persistence/index.js.map +1 -0
- package/dist/persistence/sessionSnapshot.d.ts +37 -0
- package/dist/persistence/sessionSnapshot.d.ts.map +1 -0
- package/dist/persistence/sessionSnapshot.js +53 -0
- package/dist/persistence/sessionSnapshot.js.map +1 -0
- package/dist/persistence/sqliteSessionStore.d.ts +38 -0
- package/dist/persistence/sqliteSessionStore.d.ts.map +1 -0
- package/dist/persistence/sqliteSessionStore.js +75 -0
- package/dist/persistence/sqliteSessionStore.js.map +1 -0
- package/dist/tools/agentBrowser.d.ts +131 -0
- package/dist/tools/agentBrowser.d.ts.map +1 -0
- package/dist/tools/agentBrowser.js +747 -0
- package/dist/tools/agentBrowser.js.map +1 -0
- package/dist/tools/builtin/index.d.ts +3 -0
- package/dist/tools/builtin/index.d.ts.map +1 -1
- package/dist/tools/builtin/index.js.map +1 -1
- package/dist/tools/builtin/skill.d.ts.map +1 -1
- package/dist/tools/builtin/skill.js +5 -7
- package/dist/tools/builtin/skill.js.map +1 -1
- package/dist/types.d.ts +12 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +19 -12
- package/LICENSE +0 -201
package/README.md
CHANGED
|
@@ -24,6 +24,8 @@ pnpm install
|
|
|
24
24
|
pnpm --filter @kooka/agent-sdk build
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
+
Tip: in a clean monorepo checkout, this auto-builds missing `@kooka/core` outputs (types + runtime entrypoints).
|
|
28
|
+
|
|
27
29
|
### As a dependency (local path)
|
|
28
30
|
|
|
29
31
|
In another project’s `package.json`:
|
|
@@ -99,7 +101,31 @@ try {
|
|
|
99
101
|
}
|
|
100
102
|
```
|
|
101
103
|
|
|
102
|
-
|
|
104
|
+
## Custom Tools
|
|
105
|
+
|
|
106
|
+
`createLingyunAgent(...)` returns a `ToolRegistry` so hosts can register their own tools:
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
import { createLingyunAgent, type ToolDefinition } from '@kooka/agent-sdk';
|
|
110
|
+
|
|
111
|
+
const { agent, registry } = createLingyunAgent({ /* ... */ });
|
|
112
|
+
|
|
113
|
+
const timeTool: ToolDefinition = {
|
|
114
|
+
id: 'time.now',
|
|
115
|
+
name: 'time.now',
|
|
116
|
+
description: 'Get the current time as an ISO string.',
|
|
117
|
+
parameters: { type: 'object', properties: {} },
|
|
118
|
+
execution: { type: 'function', handler: 'time.now' },
|
|
119
|
+
metadata: { readOnly: true },
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
registry.registerTool(timeTool, async () => ({
|
|
123
|
+
success: true,
|
|
124
|
+
data: { now: new Date().toISOString() },
|
|
125
|
+
}));
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## CommonJS
|
|
103
129
|
|
|
104
130
|
```js
|
|
105
131
|
const { createLingyunAgent, LingyunSession } = await import('@kooka/agent-sdk')
|
|
@@ -136,6 +162,7 @@ Useful event types:
|
|
|
136
162
|
|
|
137
163
|
- `assistant_token`: user-facing assistant text (with `<think>` and tool-call markers removed)
|
|
138
164
|
- `thought_token`: model “thinking” tokens (only if the provider emits them)
|
|
165
|
+
- `notice`: user-facing notices from the runtime (e.g. unknown `$skill-name`)
|
|
139
166
|
- `tool_call` / `tool_result` / `tool_blocked`: tool lifecycle
|
|
140
167
|
- `compaction_start` / `compaction_end`: context overflow mitigation
|
|
141
168
|
|
|
@@ -153,6 +180,38 @@ You can disable built-ins:
|
|
|
153
180
|
createLingyunAgent({ llm: { /*...*/ }, tools: { builtin: false } })
|
|
154
181
|
```
|
|
155
182
|
|
|
183
|
+
### Skills (`$skill-name`)
|
|
184
|
+
|
|
185
|
+
The SDK supports Codex-style `$skill-name` mentions.
|
|
186
|
+
If a user message includes `$<skill-name>`, LingYun:
|
|
187
|
+
|
|
188
|
+
1. Looks up the skill by `name:` in discovered `SKILL.md` files
|
|
189
|
+
2. Injects the skill body as a synthetic `<skill>...</skill>` user message before calling the model
|
|
190
|
+
|
|
191
|
+
Unknown skills are ignored and emitted as `notice` events (`callbacks.onNotice`).
|
|
192
|
+
|
|
193
|
+
Configure discovery/injection via `tools.builtinOptions.skills`:
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
createLingyunAgent({
|
|
197
|
+
llm: { provider: 'openaiCompatible', baseURL: 'http://localhost:8080/v1', model: 'your-model-id' },
|
|
198
|
+
workspaceRoot: process.cwd(),
|
|
199
|
+
tools: {
|
|
200
|
+
builtinOptions: {
|
|
201
|
+
skills: {
|
|
202
|
+
enabled: true,
|
|
203
|
+
paths: ['.lingyun/skills', '~/.codex/skills'],
|
|
204
|
+
maxPromptSkills: 50,
|
|
205
|
+
maxInjectSkills: 5,
|
|
206
|
+
maxInjectChars: 20_000,
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
});
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Note: the “Available skills” list included in the system prompt is built once per `LingyunAgent` instance. Create a new agent to refresh it.
|
|
214
|
+
|
|
156
215
|
### Approvals
|
|
157
216
|
|
|
158
217
|
Tools can require approval via `ToolDefinition.metadata.requiresApproval`.
|
|
@@ -191,6 +250,43 @@ registry.registerTool(
|
|
|
191
250
|
|
|
192
251
|
Return formatting hints via `ToolResult.metadata.outputText` / `title` to control what the agent sees.
|
|
193
252
|
|
|
253
|
+
### Browser automation (agent-browser)
|
|
254
|
+
|
|
255
|
+
If you install `agent-browser` and Chromium, you can register an **interactive browser toolset** (sessions + snapshot + actions):
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
npm i -g agent-browser
|
|
259
|
+
agent-browser install
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Then in your host:
|
|
263
|
+
|
|
264
|
+
```ts
|
|
265
|
+
import { createLingyunAgent, registerAgentBrowserTools } from '@kooka/agent-sdk';
|
|
266
|
+
|
|
267
|
+
const { registry } = createLingyunAgent({
|
|
268
|
+
llm: { provider: 'openaiCompatible', baseURL: 'http://localhost:8080/v1', model: 'your-model-id' },
|
|
269
|
+
workspaceRoot: process.cwd(),
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
registerAgentBrowserTools(registry, {
|
|
273
|
+
artifactsDir: '.kooka/agent-browser',
|
|
274
|
+
timeoutMs: 30_000,
|
|
275
|
+
});
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
Tools:
|
|
279
|
+
- `browser.startSession` / `browser.closeSession`
|
|
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
|
+
|
|
283
|
+
Security defaults:
|
|
284
|
+
- HTTPS-only and blocks private hosts / IPs by default
|
|
285
|
+
- No cookies/storage/state/headers APIs are exposed by this toolset (no auth-state support)
|
|
286
|
+
- Screenshot/PDF/trace artifacts are written under `artifactsDir` (relative to `workspaceRoot` when set)
|
|
287
|
+
|
|
288
|
+
If `agent-browser` is not on PATH, set `AGENT_BROWSER_BIN` or pass `agentBrowserBin` to `registerAgentBrowserTools`.
|
|
289
|
+
|
|
194
290
|
## Inspiration / Compatibility
|
|
195
291
|
|
|
196
292
|
- **OpenCode SDK**: OpenCode’s JavaScript SDK primarily wraps an HTTP server (client + server helpers). LingYun SDK starts with an **in‑process** agent runtime that can later be wrapped by an HTTP server if needed.
|
|
@@ -215,7 +311,52 @@ A session holds:
|
|
|
215
311
|
- message history (OpenCode-aligned “assistant message + parts”)
|
|
216
312
|
- any pending plan text (optional)
|
|
217
313
|
|
|
218
|
-
Sessions are serializable; persistence is the host application’s responsibility.
|
|
314
|
+
Sessions are serializable; persistence is the host application’s responsibility. The SDK does not write to disk.
|
|
315
|
+
|
|
316
|
+
You can snapshot + restore:
|
|
317
|
+
|
|
318
|
+
```ts
|
|
319
|
+
import { LingyunSession, snapshotSession, restoreSession } from '@kooka/agent-sdk';
|
|
320
|
+
|
|
321
|
+
const session = new LingyunSession({ sessionId: 's1' });
|
|
322
|
+
|
|
323
|
+
const snapshot = snapshotSession(session, {
|
|
324
|
+
sessionId: 's1',
|
|
325
|
+
// includeFileHandles: false, // omit fileId/path hints if you don't want to persist them
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
// Persist `snapshot` however you want (JSON files, sqlite, postgres, ...).
|
|
329
|
+
|
|
330
|
+
const restored = restoreSession(snapshot);
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
If you want SQLite, the SDK ships a `SqliteSessionStore` that works with any driver you provide:
|
|
334
|
+
|
|
335
|
+
```ts
|
|
336
|
+
import Database from 'better-sqlite3';
|
|
337
|
+
import { SqliteSessionStore, snapshotSession, restoreSession, type SqliteDriver } from '@kooka/agent-sdk';
|
|
338
|
+
|
|
339
|
+
const db = new Database('lingyun.db');
|
|
340
|
+
|
|
341
|
+
const driver: SqliteDriver = {
|
|
342
|
+
execute: (sql, params = []) => void db.prepare(sql).run(...params),
|
|
343
|
+
queryOne: (sql, params = []) => db.prepare(sql).get(...params),
|
|
344
|
+
queryAll: (sql, params = []) => db.prepare(sql).all(...params),
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
const store = new SqliteSessionStore(driver);
|
|
348
|
+
|
|
349
|
+
const sessionId = 's1';
|
|
350
|
+
const session = new LingyunSession({ sessionId });
|
|
351
|
+
|
|
352
|
+
await store.save(sessionId, snapshotSession(session, { sessionId }));
|
|
353
|
+
const loaded = await store.load(sessionId);
|
|
354
|
+
const loadedSession = loaded ? restoreSession(loaded) : new LingyunSession({ sessionId });
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
Notes:
|
|
358
|
+
- The SDK does not bundle a SQLite client library; you bring your own (e.g. `better-sqlite3`, `sqlite3`).
|
|
359
|
+
- Session snapshots contain conversation text and may include file paths; treat persisted data as sensitive.
|
|
219
360
|
|
|
220
361
|
### Run + Streaming
|
|
221
362
|
|
package/dist/agent/agent.d.ts
CHANGED
|
@@ -17,6 +17,13 @@ export type LingyunAgentRuntimeOptions = {
|
|
|
17
17
|
plugins?: PluginManager;
|
|
18
18
|
workspaceRoot?: string;
|
|
19
19
|
allowExternalPaths?: boolean;
|
|
20
|
+
skills?: {
|
|
21
|
+
enabled?: boolean;
|
|
22
|
+
paths?: string[];
|
|
23
|
+
maxPromptSkills?: number;
|
|
24
|
+
maxInjectSkills?: number;
|
|
25
|
+
maxInjectChars?: number;
|
|
26
|
+
};
|
|
20
27
|
modelLimits?: Record<string, ModelLimit>;
|
|
21
28
|
compaction?: Partial<CompactionConfig>;
|
|
22
29
|
};
|
|
@@ -27,6 +34,8 @@ export declare class LingyunAgent {
|
|
|
27
34
|
private readonly plugins;
|
|
28
35
|
private readonly workspaceRoot?;
|
|
29
36
|
private allowExternalPaths;
|
|
37
|
+
private readonly skillsConfig;
|
|
38
|
+
private skillsPromptTextPromise?;
|
|
30
39
|
private readonly modelLimits?;
|
|
31
40
|
private readonly compactionConfig;
|
|
32
41
|
private registeredPluginTools;
|
|
@@ -54,9 +63,11 @@ export declare class LingyunAgent {
|
|
|
54
63
|
private decorateGlobResultWithFileHandles;
|
|
55
64
|
private createAISDKTools;
|
|
56
65
|
private filterTools;
|
|
66
|
+
private getSkillsPromptText;
|
|
57
67
|
private composeSystemPrompt;
|
|
58
68
|
private compactSessionInternal;
|
|
59
69
|
private runOnce;
|
|
70
|
+
private injectSkillsForUserText;
|
|
60
71
|
run(params: {
|
|
61
72
|
session: LingyunSession;
|
|
62
73
|
input: string;
|
|
@@ -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,12 +1,14 @@
|
|
|
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, createHistoryForModel, createUserHistoryMessage, evaluatePermission, evaluateShellCommand, extractUsageTokens, finalizeStreamingParts, findExternalPathReferencesInShellCommand, getEffectiveHistory, getMessageText, getReservedOutputTokens, isOverflow as isContextOverflow, isPathInsideWorkspace,
|
|
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';
|
|
5
5
|
import { PluginManager } from '../plugins/pluginManager.js';
|
|
6
6
|
import { insertModeReminders } from './reminders.js';
|
|
7
7
|
import { DEFAULT_SYSTEM_PROMPT } from './prompts.js';
|
|
8
8
|
import { EDIT_TOOL_IDS, MAX_TOOL_RESULT_LENGTH, THINK_BLOCK_REGEX, TOOL_BLOCK_REGEX } from './constants.js';
|
|
9
9
|
import { delay as getRetryDelayMs, retryable as getRetryableLlmError, sleep as retrySleep } from './retry.js';
|
|
10
|
+
import { DEFAULT_SKILL_PATHS } from '../tools/builtin/index.js';
|
|
11
|
+
import { getSkillIndex, loadSkillFile } from '../skills.js';
|
|
10
12
|
class AsyncQueue {
|
|
11
13
|
state = {
|
|
12
14
|
values: [],
|
|
@@ -127,6 +129,8 @@ export class LingyunAgent {
|
|
|
127
129
|
plugins;
|
|
128
130
|
workspaceRoot;
|
|
129
131
|
allowExternalPaths;
|
|
132
|
+
skillsConfig;
|
|
133
|
+
skillsPromptTextPromise;
|
|
130
134
|
modelLimits;
|
|
131
135
|
compactionConfig;
|
|
132
136
|
registeredPluginTools = new Set();
|
|
@@ -137,12 +141,31 @@ export class LingyunAgent {
|
|
|
137
141
|
this.plugins = runtime?.plugins ?? new PluginManager({ workspaceRoot: runtime?.workspaceRoot });
|
|
138
142
|
this.workspaceRoot = runtime?.workspaceRoot ? path.resolve(runtime.workspaceRoot) : undefined;
|
|
139
143
|
this.allowExternalPaths = !!runtime?.allowExternalPaths;
|
|
144
|
+
const skills = runtime?.skills ?? {};
|
|
145
|
+
const paths = Array.isArray(skills.paths) && skills.paths.length > 0 ? skills.paths : DEFAULT_SKILL_PATHS;
|
|
146
|
+
const maxPromptSkills = Number.isFinite(skills.maxPromptSkills) && skills.maxPromptSkills >= 0
|
|
147
|
+
? Math.floor(skills.maxPromptSkills)
|
|
148
|
+
: 50;
|
|
149
|
+
const maxInjectSkills = Number.isFinite(skills.maxInjectSkills) && skills.maxInjectSkills > 0
|
|
150
|
+
? Math.floor(skills.maxInjectSkills)
|
|
151
|
+
: 5;
|
|
152
|
+
const maxInjectChars = Number.isFinite(skills.maxInjectChars) && skills.maxInjectChars > 0
|
|
153
|
+
? Math.floor(skills.maxInjectChars)
|
|
154
|
+
: 20_000;
|
|
155
|
+
this.skillsConfig = {
|
|
156
|
+
enabled: skills.enabled !== false,
|
|
157
|
+
paths,
|
|
158
|
+
maxPromptSkills,
|
|
159
|
+
maxInjectSkills,
|
|
160
|
+
maxInjectChars,
|
|
161
|
+
};
|
|
140
162
|
this.modelLimits = runtime?.modelLimits;
|
|
141
163
|
const baseCompaction = {
|
|
142
164
|
auto: true,
|
|
143
165
|
prune: true,
|
|
144
166
|
pruneProtectTokens: 40_000,
|
|
145
167
|
pruneMinimumTokens: 20_000,
|
|
168
|
+
toolOutputMode: 'afterToolCall',
|
|
146
169
|
};
|
|
147
170
|
const c = runtime?.compaction ?? {};
|
|
148
171
|
this.compactionConfig = {
|
|
@@ -150,6 +173,7 @@ export class LingyunAgent {
|
|
|
150
173
|
prune: c.prune ?? baseCompaction.prune,
|
|
151
174
|
pruneProtectTokens: Math.max(0, c.pruneProtectTokens ?? baseCompaction.pruneProtectTokens),
|
|
152
175
|
pruneMinimumTokens: Math.max(0, c.pruneMinimumTokens ?? baseCompaction.pruneMinimumTokens),
|
|
176
|
+
toolOutputMode: c.toolOutputMode ?? baseCompaction.toolOutputMode,
|
|
153
177
|
};
|
|
154
178
|
}
|
|
155
179
|
updateConfig(config) {
|
|
@@ -686,11 +710,31 @@ export class LingyunAgent {
|
|
|
686
710
|
});
|
|
687
711
|
});
|
|
688
712
|
}
|
|
689
|
-
|
|
713
|
+
getSkillsPromptText() {
|
|
714
|
+
if (!this.skillsConfig.enabled)
|
|
715
|
+
return Promise.resolve(undefined);
|
|
716
|
+
if (this.skillsPromptTextPromise)
|
|
717
|
+
return this.skillsPromptTextPromise;
|
|
718
|
+
const { paths, maxPromptSkills } = this.skillsConfig;
|
|
719
|
+
this.skillsPromptTextPromise = getSkillIndex({
|
|
720
|
+
workspaceRoot: this.workspaceRoot,
|
|
721
|
+
searchPaths: paths,
|
|
722
|
+
allowExternalPaths: this.allowExternalPaths,
|
|
723
|
+
})
|
|
724
|
+
.then((index) => renderSkillsSectionForPrompt({
|
|
725
|
+
skills: index.skills,
|
|
726
|
+
maxSkills: maxPromptSkills,
|
|
727
|
+
workspaceRoot: this.workspaceRoot,
|
|
728
|
+
}))
|
|
729
|
+
.catch(() => undefined);
|
|
730
|
+
return this.skillsPromptTextPromise;
|
|
731
|
+
}
|
|
732
|
+
async composeSystemPrompt(modelId) {
|
|
690
733
|
const basePrompt = this.config.systemPrompt || DEFAULT_SYSTEM_PROMPT;
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
734
|
+
const skillsPromptText = await this.getSkillsPromptText();
|
|
735
|
+
const system = [basePrompt, skillsPromptText].filter(Boolean);
|
|
736
|
+
const out = await this.plugins.trigger('experimental.chat.system.transform', { sessionId: this.config.sessionId, mode: this.getMode(), modelId }, { system });
|
|
737
|
+
return Array.isArray(out.system) ? out.system.filter(Boolean) : system;
|
|
694
738
|
}
|
|
695
739
|
async compactSessionInternal(session, params, callbacks) {
|
|
696
740
|
const maybeAwait = async (value) => {
|
|
@@ -721,7 +765,7 @@ export class LingyunAgent {
|
|
|
721
765
|
middleware: [extractReasoningMiddleware({ tagName: 'think', startWithReasoning: false })],
|
|
722
766
|
});
|
|
723
767
|
const effective = getEffectiveHistory(session.history);
|
|
724
|
-
const prepared =
|
|
768
|
+
const prepared = createHistoryForCompactionPrompt(effective, this.compactionConfig);
|
|
725
769
|
const withoutIds = prepared.map(({ id: _id, ...rest }) => rest);
|
|
726
770
|
const compactionUser = createUserHistoryMessage(promptText, { synthetic: true });
|
|
727
771
|
const compactionModelMessages = await convertToModelMessages([...withoutIds, compactionUser], { tools: {} });
|
|
@@ -964,7 +1008,9 @@ export class LingyunAgent {
|
|
|
964
1008
|
session.history.push(assistantMessage);
|
|
965
1009
|
const lastAssistantText = getMessageText(assistantMessage).trim();
|
|
966
1010
|
lastResponse = lastAssistantText || lastResponse;
|
|
967
|
-
|
|
1011
|
+
if (this.compactionConfig.prune && this.compactionConfig.toolOutputMode === 'afterToolCall') {
|
|
1012
|
+
markPreviousAssistantToolOutputs(session.history);
|
|
1013
|
+
}
|
|
968
1014
|
await Promise.resolve(callbacksSafe?.onIterationEnd?.(iteration));
|
|
969
1015
|
const modelLimit = this.getModelLimit(modelId);
|
|
970
1016
|
const reservedOutputTokens = getReservedOutputTokens({ modelLimit, maxOutputTokens: this.getMaxOutputTokens() });
|
|
@@ -990,6 +1036,77 @@ export class LingyunAgent {
|
|
|
990
1036
|
callbacksSafe?.onComplete?.(lastResponse);
|
|
991
1037
|
return lastResponse;
|
|
992
1038
|
}
|
|
1039
|
+
async injectSkillsForUserText(session, text, callbacks, signal) {
|
|
1040
|
+
if (!this.skillsConfig.enabled)
|
|
1041
|
+
return;
|
|
1042
|
+
const mentions = extractSkillMentions(text);
|
|
1043
|
+
if (mentions.length === 0)
|
|
1044
|
+
return;
|
|
1045
|
+
const index = await getSkillIndex({
|
|
1046
|
+
workspaceRoot: this.workspaceRoot,
|
|
1047
|
+
searchPaths: this.skillsConfig.paths,
|
|
1048
|
+
allowExternalPaths: this.allowExternalPaths,
|
|
1049
|
+
signal,
|
|
1050
|
+
});
|
|
1051
|
+
const { selected, unknown } = selectSkillsForText(text, index);
|
|
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
|
+
}
|
|
1064
|
+
if (selected.length === 0)
|
|
1065
|
+
return;
|
|
1066
|
+
const maxSkills = this.skillsConfig.maxInjectSkills;
|
|
1067
|
+
const maxChars = this.skillsConfig.maxInjectChars;
|
|
1068
|
+
const selectedForInject = selected.slice(0, maxSkills);
|
|
1069
|
+
const activeLabel = selectedForInject.map((s) => `$${s.name}`).join(', ');
|
|
1070
|
+
const blocks = [];
|
|
1071
|
+
if (activeLabel) {
|
|
1072
|
+
blocks.push([
|
|
1073
|
+
'<skills>',
|
|
1074
|
+
`<active>${activeLabel}</active>`,
|
|
1075
|
+
'You MUST apply ALL active skills for the next user request.',
|
|
1076
|
+
'Treat skill instructions as additive. If they conflict, call it out and ask the user how to proceed (do not ignore a skill silently).',
|
|
1077
|
+
'</skills>',
|
|
1078
|
+
].join('\n'));
|
|
1079
|
+
}
|
|
1080
|
+
for (const skill of selectedForInject) {
|
|
1081
|
+
if (signal?.aborted)
|
|
1082
|
+
break;
|
|
1083
|
+
let body;
|
|
1084
|
+
try {
|
|
1085
|
+
body = (await loadSkillFile(skill)).content;
|
|
1086
|
+
}
|
|
1087
|
+
catch {
|
|
1088
|
+
continue;
|
|
1089
|
+
}
|
|
1090
|
+
let truncated = false;
|
|
1091
|
+
if (body.length > maxChars) {
|
|
1092
|
+
body = body.slice(0, maxChars);
|
|
1093
|
+
truncated = true;
|
|
1094
|
+
}
|
|
1095
|
+
blocks.push([
|
|
1096
|
+
'<skill>',
|
|
1097
|
+
`<name>${skill.name}</name>`,
|
|
1098
|
+
`<path>${redactFsPathForPrompt(skill.filePath, { workspaceRoot: this.workspaceRoot })}</path>`,
|
|
1099
|
+
body.trimEnd(),
|
|
1100
|
+
truncated ? '\n\n... [TRUNCATED]' : '',
|
|
1101
|
+
'</skill>',
|
|
1102
|
+
]
|
|
1103
|
+
.filter(Boolean)
|
|
1104
|
+
.join('\n'));
|
|
1105
|
+
}
|
|
1106
|
+
if (blocks.length > 0) {
|
|
1107
|
+
session.history.push(createUserHistoryMessage(blocks.join('\n\n'), { synthetic: true, skill: true }));
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
993
1110
|
run(params) {
|
|
994
1111
|
const queue = new AsyncQueue();
|
|
995
1112
|
const callbacks = params.callbacks;
|
|
@@ -999,6 +1116,10 @@ export class LingyunAgent {
|
|
|
999
1116
|
callbacks?.onDebug?.(message);
|
|
1000
1117
|
queue.push({ type: 'debug', message });
|
|
1001
1118
|
},
|
|
1119
|
+
onNotice: (notice) => {
|
|
1120
|
+
callbacks?.onNotice?.(notice);
|
|
1121
|
+
queue.push({ type: 'notice', notice });
|
|
1122
|
+
},
|
|
1002
1123
|
onStatusChange: (status) => {
|
|
1003
1124
|
callbacks?.onStatusChange?.(status);
|
|
1004
1125
|
queue.push({ type: 'status', status: status });
|
|
@@ -1041,6 +1162,7 @@ export class LingyunAgent {
|
|
|
1041
1162
|
};
|
|
1042
1163
|
const done = (async () => {
|
|
1043
1164
|
try {
|
|
1165
|
+
await this.injectSkillsForUserText(params.session, params.input, proxy, params.signal);
|
|
1044
1166
|
params.session.history.push(createUserHistoryMessage(params.input));
|
|
1045
1167
|
const text = await this.runOnce(params.session, proxy, params.signal);
|
|
1046
1168
|
queue.push({ type: 'status', status: { type: 'done', message: '' } });
|
|
@@ -1054,6 +1176,9 @@ export class LingyunAgent {
|
|
|
1054
1176
|
queue.fail(err);
|
|
1055
1177
|
throw err;
|
|
1056
1178
|
}
|
|
1179
|
+
finally {
|
|
1180
|
+
params.session.history = params.session.history.filter((msg) => !(msg.role === 'user' && msg.metadata?.skill));
|
|
1181
|
+
}
|
|
1057
1182
|
})();
|
|
1058
1183
|
return { events: queue, done };
|
|
1059
1184
|
}
|