@dex-ai/coding-agent-sdk 0.1.21

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.
@@ -0,0 +1,416 @@
1
+ /**
2
+ * Settings Extension — loads and manages ~/.dex/settings.json (global user settings).
3
+ *
4
+ * Responsibilities:
5
+ * - Load permission mode, allowed/denied tool lists on init
6
+ * - Expose settings via actx.state.get("settings")
7
+ * - Provide mutation methods for runtime changes (mode cycling, allow-always)
8
+ * - Persist changes back to disk
9
+ *
10
+ * Other extensions (e.g. approval) read from actx.state.get("settings").
11
+ */
12
+
13
+ import type { Extension, AgentContext, ThinkingLevel } from "@dex-ai/sdk";
14
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
15
+ import { join } from "node:path";
16
+ import { homedir } from "node:os";
17
+ import type { PermissionMode, PermissionSettings } from "../types";
18
+
19
+ /* ------------------------------------------------------------------ */
20
+ /* Types */
21
+ /* ------------------------------------------------------------------ */
22
+
23
+ export interface GlobalSettings {
24
+ readonly permissions: PermissionSettings;
25
+ /** Enable debug mode (stderr capture + /debug panel). Default: false. */
26
+ readonly debug?: boolean;
27
+ /** Hide thinking/reasoning text from display. Default: false (show thinking). */
28
+ readonly hideThinking?: boolean;
29
+ /** Default thinking/reasoning level. Default: "high". */
30
+ readonly defaultThinking?: ThinkingLevel | undefined;
31
+ /** Configured providers (name → config). */
32
+ readonly providers?: Record<string, ProviderSettingsConfig>;
33
+ /** Default provider name. */
34
+ readonly defaultProvider?: string;
35
+ /** Default model ID. */
36
+ readonly defaultModel?: string;
37
+ }
38
+
39
+ export interface ProviderSettingsConfig {
40
+ readonly type: string;
41
+ readonly apiKey?: string;
42
+ readonly baseUrl?: string;
43
+ readonly models?: Array<
44
+ string | { id: string; contextWindow?: number; maxTokens?: number }
45
+ >;
46
+ }
47
+
48
+ /* ------------------------------------------------------------------ */
49
+ /* File I/O */
50
+ /* ------------------------------------------------------------------ */
51
+
52
+ const DEX_DIR = join(homedir(), ".dex");
53
+ const AGENT_DIR = join(DEX_DIR, "agent");
54
+ const SETTINGS_PATH = join(AGENT_DIR, "settings.json");
55
+ const STATE_PATH = join(AGENT_DIR, "state.json");
56
+
57
+ const DEFAULT_SETTINGS: GlobalSettings = {
58
+ permissions: {
59
+ mode: "auto",
60
+ allowedTools: [],
61
+ deniedTools: [],
62
+ },
63
+ };
64
+
65
+ function validateMode(mode: unknown): PermissionMode | undefined {
66
+ if (mode === "read" || mode === "auto" || mode === "yolo") return mode;
67
+ return undefined;
68
+ }
69
+
70
+ const THINKING_LEVELS: ThinkingLevel[] = [
71
+ "off",
72
+ "min",
73
+ "low",
74
+ "med",
75
+ "high",
76
+ "max",
77
+ ];
78
+
79
+ function validateThinkingLevel(level: unknown): ThinkingLevel | undefined {
80
+ if (
81
+ typeof level === "string" &&
82
+ THINKING_LEVELS.includes(level as ThinkingLevel)
83
+ ) {
84
+ return level as ThinkingLevel;
85
+ }
86
+ return undefined;
87
+ }
88
+
89
+ /**
90
+ * Ensure ~/.dex/agent/settings.json exists.
91
+ * Creates an empty file if nothing exists.
92
+ */
93
+ export function ensureSettingsFile(): void {
94
+ if (existsSync(SETTINGS_PATH)) return;
95
+
96
+ mkdirSync(AGENT_DIR, { recursive: true });
97
+ writeFileSync(SETTINGS_PATH, "{}\n", "utf-8");
98
+ }
99
+
100
+ /**
101
+ * Parse a settings JSON object into a validated GlobalSettings.
102
+ */
103
+ function parseSettings(parsed: Partial<GlobalSettings>): GlobalSettings {
104
+ return {
105
+ permissions: {
106
+ mode: validateMode(parsed.permissions?.mode) ?? "auto",
107
+ allowedTools: Array.isArray(parsed.permissions?.allowedTools)
108
+ ? parsed.permissions!.allowedTools
109
+ : [],
110
+ deniedTools: Array.isArray(parsed.permissions?.deniedTools)
111
+ ? parsed.permissions!.deniedTools
112
+ : [],
113
+ },
114
+ ...(parsed.debug === true ? { debug: true } : {}),
115
+ ...(parsed.hideThinking === true ? { hideThinking: true } : {}),
116
+ ...(validateThinkingLevel(parsed.defaultThinking)
117
+ ? { defaultThinking: validateThinkingLevel(parsed.defaultThinking) }
118
+ : {}),
119
+ ...(parsed.providers
120
+ ? {
121
+ providers: parsed.providers as Record<string, ProviderSettingsConfig>,
122
+ }
123
+ : {}),
124
+ ...(parsed.defaultProvider
125
+ ? { defaultProvider: parsed.defaultProvider }
126
+ : {}),
127
+ ...(parsed.defaultModel ? { defaultModel: parsed.defaultModel } : {}),
128
+ };
129
+ }
130
+
131
+ /**
132
+ * Load a settings file from a path, returning null if not found/invalid.
133
+ */
134
+ function loadSettingsFile(path: string): GlobalSettings | null {
135
+ if (!existsSync(path)) return null;
136
+ try {
137
+ const raw = readFileSync(path, "utf-8");
138
+ const parsed = JSON.parse(raw) as Partial<GlobalSettings>;
139
+ return parseSettings(parsed);
140
+ } catch {
141
+ return null;
142
+ }
143
+ }
144
+
145
+ /**
146
+ * Load global settings from ~/.dex/agent/settings.json.
147
+ * Returns defaults if the file doesn't exist or is invalid.
148
+ */
149
+ export function loadGlobalSettings(): GlobalSettings {
150
+ return loadSettingsFile(SETTINGS_PATH) ?? DEFAULT_SETTINGS;
151
+ }
152
+
153
+ /**
154
+ * Load resolved settings: global merged with workspace overrides.
155
+ * Workspace .dex/agent/settings.json overrides global values.
156
+ */
157
+ export function loadResolvedSettings(workspaceDexDir?: string): GlobalSettings {
158
+ const global = loadGlobalSettings();
159
+ if (!workspaceDexDir) return global;
160
+
161
+ const workspacePath = join(workspaceDexDir, "agent", "settings.json");
162
+ const workspace = loadSettingsFile(workspacePath);
163
+ if (!workspace) return global;
164
+
165
+ // Workspace overrides global (shallow merge per section)
166
+ return {
167
+ ...global,
168
+ ...workspace,
169
+ permissions: {
170
+ ...global.permissions,
171
+ ...workspace.permissions,
172
+ },
173
+ // Providers: merge workspace providers on top of global
174
+ ...(global.providers || workspace.providers
175
+ ? { providers: { ...global.providers, ...workspace.providers } }
176
+ : {}),
177
+ };
178
+ }
179
+
180
+ /**
181
+ * Save global settings to ~/.dex/agent/settings.json.
182
+ */
183
+ export function saveGlobalSettings(settings: GlobalSettings): void {
184
+ mkdirSync(AGENT_DIR, { recursive: true });
185
+ writeFileSync(
186
+ SETTINGS_PATH,
187
+ JSON.stringify(settings, null, 2) + "\n",
188
+ "utf-8",
189
+ );
190
+ }
191
+
192
+ /* ------------------------------------------------------------------ */
193
+ /* Mutation helpers (used by the extension and exported for CLI) */
194
+ /* ------------------------------------------------------------------ */
195
+
196
+ /**
197
+ * Add a tool to the always-allowed list and persist.
198
+ */
199
+ export function addAllowedTool(toolName: string): GlobalSettings {
200
+ const settings = loadGlobalSettings();
201
+ const allowed = new Set(settings.permissions.allowedTools ?? []);
202
+ allowed.add(toolName);
203
+ const updated: GlobalSettings = {
204
+ ...settings,
205
+ permissions: {
206
+ ...settings.permissions,
207
+ allowedTools: [...allowed],
208
+ },
209
+ };
210
+ saveGlobalSettings(updated);
211
+ return updated;
212
+ }
213
+
214
+ /**
215
+ * Update the permission mode and persist.
216
+ */
217
+ export function setPermissionMode(mode: PermissionMode): GlobalSettings {
218
+ const settings = loadGlobalSettings();
219
+ const updated: GlobalSettings = {
220
+ ...settings,
221
+ permissions: {
222
+ ...settings.permissions,
223
+ mode,
224
+ },
225
+ };
226
+ saveGlobalSettings(updated);
227
+ return updated;
228
+ }
229
+
230
+ /**
231
+ * Set the default thinking level and persist.
232
+ */
233
+ export function setDefaultThinking(level: ThinkingLevel): GlobalSettings {
234
+ const settings = loadGlobalSettings();
235
+ const updated: GlobalSettings = {
236
+ ...settings,
237
+ defaultThinking: level === "off" ? undefined : level,
238
+ };
239
+ // Remove the key entirely if off (clean JSON)
240
+ if (level === "off") {
241
+ delete (updated as any).defaultThinking;
242
+ }
243
+ saveGlobalSettings(updated);
244
+ return updated;
245
+ }
246
+
247
+ /**
248
+ * Add or update a provider config and persist.
249
+ */
250
+ export function saveProviderConfig(
251
+ name: string,
252
+ config: ProviderSettingsConfig,
253
+ ): GlobalSettings {
254
+ const settings = loadGlobalSettings();
255
+ const providers = { ...(settings.providers ?? {}), [name]: config };
256
+ const updated: GlobalSettings = { ...settings, providers };
257
+ saveGlobalSettings(updated);
258
+ return updated;
259
+ }
260
+
261
+ /**
262
+ * Set the default provider and model, and persist.
263
+ */
264
+ export function setDefaultProvider(
265
+ providerName: string,
266
+ model?: string,
267
+ ): GlobalSettings {
268
+ const settings = loadGlobalSettings();
269
+ const updated: GlobalSettings = {
270
+ ...settings,
271
+ defaultProvider: providerName,
272
+ ...(model ? { defaultModel: model } : {}),
273
+ };
274
+ saveGlobalSettings(updated);
275
+ return updated;
276
+ }
277
+
278
+ /**
279
+ * Transient agent state (persisted separately from settings).
280
+ */
281
+ export interface AgentState {
282
+ lastProvider?: string;
283
+ lastModel?: string;
284
+ }
285
+
286
+ /**
287
+ * Load transient agent state from ~/.dex/agent/state.json.
288
+ */
289
+ export function loadAgentState(): AgentState {
290
+ try {
291
+ if (!existsSync(STATE_PATH)) return {};
292
+ const raw = JSON.parse(readFileSync(STATE_PATH, "utf-8"));
293
+ return raw && typeof raw === "object" ? raw : {};
294
+ } catch {
295
+ return {};
296
+ }
297
+ }
298
+
299
+ /**
300
+ * Save transient agent state to ~/.dex/agent/state.json.
301
+ */
302
+ function saveAgentState(state: AgentState): void {
303
+ mkdirSync(AGENT_DIR, { recursive: true });
304
+ writeFileSync(STATE_PATH, JSON.stringify(state, null, 2) + "\n", "utf-8");
305
+ }
306
+
307
+ /**
308
+ * Save the last used provider/model (auto-persisted on model switch).
309
+ */
310
+ export function saveLastUsedModel(providerName: string, model: string): void {
311
+ // Don't persist empty values — only save when there's an actual selection
312
+ if (!providerName && !model) return;
313
+ const state = loadAgentState();
314
+ const updated: AgentState = {
315
+ ...state,
316
+ ...(providerName ? { lastProvider: providerName } : {}),
317
+ ...(model ? { lastModel: model } : {}),
318
+ };
319
+ saveAgentState(updated);
320
+ }
321
+
322
+ /* ------------------------------------------------------------------ */
323
+ /* Extension */
324
+ /* ------------------------------------------------------------------ */
325
+
326
+ export interface SettingsExtensionState {
327
+ /** Current permission mode (mutable at runtime). */
328
+ permissionMode: PermissionMode;
329
+ /** Current thinking level (mutable at runtime). undefined means off. */
330
+ thinkingLevel: ThinkingLevel | undefined;
331
+ /** Set of always-allowed tools. */
332
+ readonly allowedTools: Set<string>;
333
+ /** Set of always-denied tools. */
334
+ readonly deniedTools: Set<string>;
335
+ /** Cycle to the next permission mode. Returns the new mode. */
336
+ cycleMode(): PermissionMode;
337
+ /** Set the thinking level and persist. */
338
+ setThinking(level: ThinkingLevel): void;
339
+ /** Cycle to the next thinking level. Returns the new level. */
340
+ cycleThinking(): ThinkingLevel;
341
+ /** Add a tool to the always-allowed list (persists). */
342
+ allowAlways(toolName: string): void;
343
+ /** Reload settings from disk. */
344
+ reload(): void;
345
+ }
346
+
347
+ export function settingsExtension(): Extension {
348
+ return {
349
+ name: "settings",
350
+
351
+ init(actx: AgentContext) {
352
+ // Ensure agent/settings.json exists (migrates from legacy if needed)
353
+ ensureSettingsFile();
354
+
355
+ const workspace = actx.state.get("workspace") as
356
+ | { dexDir?: string }
357
+ | undefined;
358
+ let settings = loadResolvedSettings(workspace?.dexDir);
359
+
360
+ const state: SettingsExtensionState = {
361
+ permissionMode: settings.permissions.mode,
362
+ thinkingLevel: settings.defaultThinking ?? "high",
363
+ allowedTools: new Set(settings.permissions.allowedTools ?? []),
364
+ deniedTools: new Set(settings.permissions.deniedTools ?? []),
365
+
366
+ cycleMode(): PermissionMode {
367
+ const modes: PermissionMode[] = ["read", "auto", "yolo"];
368
+ const idx = modes.indexOf(state.permissionMode);
369
+ const next = modes[(idx + 1) % modes.length]!;
370
+ state.permissionMode = next;
371
+ setPermissionMode(next);
372
+ return next;
373
+ },
374
+
375
+ setThinking(level: ThinkingLevel): void {
376
+ state.thinkingLevel = level === "off" ? undefined : level;
377
+ setDefaultThinking(level);
378
+ },
379
+
380
+ cycleThinking(): ThinkingLevel {
381
+ const current = state.thinkingLevel ?? "off";
382
+ const idx = THINKING_LEVELS.indexOf(current);
383
+ const next = THINKING_LEVELS[(idx + 1) % THINKING_LEVELS.length]!;
384
+ state.thinkingLevel = next === "off" ? undefined : next;
385
+ setDefaultThinking(next);
386
+ return next;
387
+ },
388
+
389
+ allowAlways(toolName: string): void {
390
+ const updated = addAllowedTool(toolName);
391
+ state.allowedTools.add(toolName);
392
+ // Sync the full state
393
+ for (const t of updated.permissions.allowedTools ?? []) {
394
+ state.allowedTools.add(t);
395
+ }
396
+ },
397
+
398
+ reload(): void {
399
+ settings = loadGlobalSettings();
400
+ state.permissionMode = settings.permissions.mode;
401
+ state.thinkingLevel = settings.defaultThinking ?? "high";
402
+ state.allowedTools.clear();
403
+ for (const t of settings.permissions.allowedTools ?? []) {
404
+ state.allowedTools.add(t);
405
+ }
406
+ state.deniedTools.clear();
407
+ for (const t of settings.permissions.deniedTools ?? []) {
408
+ state.deniedTools.add(t);
409
+ }
410
+ },
411
+ };
412
+
413
+ actx.state.set("settings", state);
414
+ },
415
+ };
416
+ }
@@ -0,0 +1,208 @@
1
+ /**
2
+ * System Prompt — the core coding assistant identity, tool usage guidelines,
3
+ * safety rules, and output format.
4
+ *
5
+ * This is injected as the actual system prompt via Agent.create({ systemPrompt })
6
+ * so it's always in context without needing a get_skill() call.
7
+ */
8
+
9
+ /* ------------------------------------------------------------------ */
10
+ /* Prompt sections */
11
+ /* ------------------------------------------------------------------ */
12
+
13
+ const SYSTEM_PROMPT = `You are Dex, an expert AI coding agent. You help users understand, write, debug, and improve code by directly reading, editing, and creating files in their projects.
14
+
15
+ ## Principles
16
+
17
+ - Be concise. Explain only what's needed.
18
+ - Show code, not just descriptions. Use fenced code blocks with language tags.
19
+ - Prefer small, targeted changes over large rewrites.
20
+ - Read before writing. Always understand existing code before modifying it.
21
+ - Preserve existing style and conventions in the codebase.
22
+ - Ask for clarification when requirements are ambiguous.
23
+ - Take initiative — when the intent is clear, act rather than asking for permission.
24
+ - Think step by step for complex tasks, but execute without unnecessary narration.`;
25
+
26
+ const TOOL_GUIDELINES = `## Tool Usage Guidelines
27
+
28
+ - **read**: Always read a file before editing it. Never assume file contents.
29
+ - **edit**: Prefer edit over write for existing files. Provide enough context in the oldString to match uniquely.
30
+ - **write**: Use only for new files or complete rewrites. Never overwrite without reading first.
31
+ - **search**: Use to find relevant code before making changes. Search for function names, imports, and related patterns.
32
+ - **bash**: Use for running tests, installing dependencies, checking git status, and other shell operations.
33
+ - Prefer non-destructive commands. Avoid \`rm -rf\`, \`git reset --hard\`, etc. without explicit user approval.
34
+ - Keep commands focused and single-purpose.
35
+ - Always quote file paths that may contain spaces.
36
+ - **OUTPUT EFFICIENCY IS CRITICAL.** Every byte of tool output consumes context window tokens. Unbounded output degrades performance and can truncate important information.
37
+ - ALWAYS constrain output: pipe through \`| tail -n 20\`, \`| head -n 30\`, \`| grep -E 'pattern'\`, or \`2>&1 | tail -n 50\`.
38
+ - For builds/tests: use \`2>&1 | tail -n 30\` or \`2>&1 | grep -A 3 'error\\|fail\\|Error'\` to capture only relevant diagnostics.
39
+ - For listing files: use \`| head -n 20\` or combine with \`grep\` to filter. Never dump entire directory trees.
40
+ - For git: \`git log --oneline -10\`, \`git diff --stat\`, or \`git diff -- file.ts | head -50\`.
41
+ - For \`find\`: always add \`| head -n 20\` or narrow the search with specific patterns.
42
+ - For \`cat\`/viewing: prefer the \`read\` tool instead. If you must use cat, pipe through \`head\` or \`sed -n '10,30p'\`.
43
+ - Combine filters: \`command 2>&1 | grep -v 'noise' | tail -n 20\`.
44
+ - NEVER run a command that may produce unbounded output without piping through a limiter.
45
+ - If a command produces no useful output on success, append \`&& echo 'done'\` rather than dumping stdout.
46
+
47
+ When making function calls using tools that accept array or object parameters ensure those are structured using JSON. For example:
48
+ \`\`\`json
49
+ {"edits": [{"oldString": "foo", "newString": "bar"}]}
50
+ \`\`\`
51
+
52
+ String and scalar parameters should be specified as is, while lists and objects should use JSON format.`;
53
+
54
+ const SAFETY_RULES = `## Safety Rules
55
+
56
+ - Never commit secrets, API keys, passwords, or credentials to files.
57
+ - Never run destructive commands (delete, force push, reset) without user confirmation.
58
+ - Never modify files outside the project working directory unless explicitly asked.
59
+ - If a command might have side effects (network calls, installs, writes), explain before executing.
60
+ - When unsure about the impact of a change, explain the risks and ask for confirmation.`;
61
+
62
+ const OUTPUT_FORMAT = `## Output Format
63
+
64
+ - Use fenced code blocks with language identifiers for all code.
65
+ - When showing changes, include enough surrounding context for the user to locate them.
66
+ - For multi-step tasks, outline the plan briefly before executing.
67
+ - After completing a task, summarize what was done and any follow-up needed.`;
68
+
69
+ const SKILL_USAGE = `## Skill Usage
70
+
71
+ Skills are your expert playbooks. Use them proactively — don't just rely on general knowledge.
72
+
73
+ - **Load skills early.** When starting a task, check if a relevant skill exists in the catalog and \`get_skill\` it before diving in.
74
+ - **Switch skills as you go.** Complex tasks span multiple phases (design → implement → debug → review). Load the appropriate skill for each phase rather than working from memory alone.
75
+ - **Follow skill workflows.** Skills encode tested procedures. When a loaded skill prescribes specific steps, follow them rather than improvising.
76
+ - **Combine skills.** Many tasks benefit from layering — e.g. use \`debug-investigate\` to find the issue, then \`spec-implementation\` to fix it properly.
77
+ - **Don't hoard.** You don't need to load every skill upfront. Load what you need for the current phase, then load the next when you transition.`;
78
+
79
+ const EXTENDING_DEX = `## Extending Dex
80
+
81
+ You can create custom tools and skills by writing extensions.
82
+
83
+ ### Extension Locations
84
+
85
+ Extensions are loaded in layers (later layers override earlier ones):
86
+
87
+ 1. **Built-in** — bundled with Dex (tools, skills, providers)
88
+ 2. **Global** — \`~/.dex/extensions/\` — available in all projects
89
+ 3. **Project** — \`<project>/.dex/extensions/\` — scoped to a single project
90
+
91
+ ### Creating a Tool
92
+
93
+ A tool is an action the model can invoke. Create an extension file:
94
+
95
+ \`\`\`typescript
96
+ import { Extension, Tool } from "@dex-ai/sdk";
97
+ import { z } from "zod";
98
+
99
+ export default Extension.define({
100
+ name: "my-tools",
101
+ tools: [
102
+ Tool.define({
103
+ name: "my_tool",
104
+ description: "What the tool does",
105
+ parameters: z.object({
106
+ input: z.string().describe("Description of the input"),
107
+ }),
108
+ access: "read", // "read" = no side effects, "write" = modifies state
109
+ async execute(input, gctx) {
110
+ // Implementation
111
+ return { result: "value" };
112
+ },
113
+ }),
114
+ ],
115
+ });
116
+ \`\`\`
117
+
118
+ ### Creating a Skill
119
+
120
+ A skill injects structured instructions into the system prompt. Create a folder:
121
+
122
+ \`\`\`
123
+ ~/.dex/skills/<skill-name>/SKILL.md
124
+ \`\`\`
125
+
126
+ With optional YAML frontmatter:
127
+
128
+ \`\`\`markdown
129
+ ---
130
+ name: my-skill
131
+ description: Short description for the catalog
132
+ ---
133
+
134
+ ## Instructions
135
+
136
+ Your skill content here (markdown).
137
+ \`\`\`
138
+
139
+ Skills can also be defined programmatically in an extension:
140
+
141
+ \`\`\`typescript
142
+ import { Extension } from "@dex-ai/sdk";
143
+
144
+ export default Extension.define({
145
+ name: "my-skills",
146
+ skills: [{
147
+ name: "my-skill",
148
+ description: "Guidance for X",
149
+ content: "## How to do X\\n\\n- Step 1...\\n- Step 2...",
150
+ when: (actx) => true, // optional: conditional activation
151
+ }],
152
+ });
153
+ \`\`\`
154
+
155
+ ### Extension Lifecycle
156
+
157
+ Extensions can also hook into events (\`on\`), provide models, declare config schemas, and define canvas panels. Use \`init()\` and \`dispose()\` for setup/teardown of long-lived resources.`;
158
+
159
+ /* ------------------------------------------------------------------ */
160
+ /* Builder */
161
+ /* ------------------------------------------------------------------ */
162
+
163
+ export interface SystemPromptOptions {
164
+ /** Override the base system prompt entirely. */
165
+ systemPrompt?: string;
166
+ /** Include tool usage guidelines. Default: true. */
167
+ toolGuidelines?: boolean;
168
+ /** Include safety rules. Default: true. */
169
+ safetyRules?: boolean;
170
+ /** Include output format guidelines. Default: true. */
171
+ outputFormat?: boolean;
172
+ /** Include skill usage instructions. Default: true. */
173
+ skillUsage?: boolean;
174
+ /** Include extending Dex instructions. Default: true. */
175
+ extendingDex?: boolean;
176
+ }
177
+
178
+ /**
179
+ * Build the Dex coding agent system prompt string.
180
+ * Composes identity, tool guidelines, safety rules, and output format.
181
+ */
182
+ export function buildSystemPrompt(opts: SystemPromptOptions = {}): string {
183
+ const parts: string[] = [];
184
+
185
+ parts.push(opts.systemPrompt ?? SYSTEM_PROMPT);
186
+
187
+ if (opts.toolGuidelines ?? true) {
188
+ parts.push(TOOL_GUIDELINES);
189
+ }
190
+
191
+ if (opts.safetyRules ?? true) {
192
+ parts.push(SAFETY_RULES);
193
+ }
194
+
195
+ if (opts.outputFormat ?? true) {
196
+ parts.push(OUTPUT_FORMAT);
197
+ }
198
+
199
+ if (opts.skillUsage ?? true) {
200
+ parts.push(SKILL_USAGE);
201
+ }
202
+
203
+ if (opts.extendingDex ?? true) {
204
+ parts.push(EXTENDING_DEX);
205
+ }
206
+
207
+ return parts.join("\n\n");
208
+ }