@leing2021/super-pi 0.22.1 → 0.23.1

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.
Files changed (117) hide show
  1. package/README.md +66 -293
  2. package/extensions/ce-core/index.ts +276 -48
  3. package/extensions/ce-core/tools/parallel-subagent.ts +3 -3
  4. package/extensions/ce-core/tools/subagent.ts +2 -2
  5. package/package.json +4 -20
  6. package/rules/common/naming.md +154 -0
  7. package/skills/01-brainstorm/SKILL.md +42 -79
  8. package/skills/01-brainstorm/references/ce-brainstorm-mode.md +47 -0
  9. package/skills/02-plan/SKILL.md +43 -61
  10. package/skills/02-plan/references/solution-search.md +46 -0
  11. package/skills/03-work/SKILL.md +34 -52
  12. package/skills/03-work/references/completion-report.md +51 -0
  13. package/skills/04-review/SKILL.md +44 -51
  14. package/skills/04-review/references/solution-search.md +46 -0
  15. package/skills/06-next/SKILL.md +35 -20
  16. package/skills/06-next/references/recommendation-logic.md +47 -2
  17. package/skills/07-worktree/SKILL.md +21 -20
  18. package/skills/08-help/SKILL.md +46 -0
  19. package/skills/08-help/references/workflow-sequence.md +135 -0
  20. package/skills/references/language-detection.md +44 -0
  21. package/LICENSE +0 -21
  22. package/THIRD-PARTY-NOTICES.md +0 -35
  23. package/extensions/subagent/__tests__/async-job-tracker.test.ts +0 -21
  24. package/extensions/subagent/__tests__/execution-activity.test.ts +0 -15
  25. package/extensions/subagent/__tests__/parallel-render-stress.test.ts +0 -82
  26. package/extensions/subagent/__tests__/render-dedup.test.ts +0 -98
  27. package/extensions/subagent/__tests__/render-widget.test.ts +0 -28
  28. package/extensions/subagent/__tests__/throttle.test.ts +0 -90
  29. package/extensions/subagent/agent-management.ts +0 -596
  30. package/extensions/subagent/agent-manager-chain-detail.ts +0 -163
  31. package/extensions/subagent/agent-manager-detail.ts +0 -232
  32. package/extensions/subagent/agent-manager-edit.ts +0 -391
  33. package/extensions/subagent/agent-manager-list.ts +0 -279
  34. package/extensions/subagent/agent-manager-parallel.ts +0 -305
  35. package/extensions/subagent/agent-manager.ts +0 -706
  36. package/extensions/subagent/agent-scope.ts +0 -9
  37. package/extensions/subagent/agent-selection.ts +0 -26
  38. package/extensions/subagent/agent-serializer.ts +0 -124
  39. package/extensions/subagent/agent-templates.ts +0 -63
  40. package/extensions/subagent/agents/context-builder.md +0 -37
  41. package/extensions/subagent/agents/delegate.md +0 -9
  42. package/extensions/subagent/agents/oracle.md +0 -73
  43. package/extensions/subagent/agents/planner.md +0 -52
  44. package/extensions/subagent/agents/researcher.md +0 -50
  45. package/extensions/subagent/agents/reviewer.md +0 -38
  46. package/extensions/subagent/agents/scout.md +0 -48
  47. package/extensions/subagent/agents/worker.md +0 -52
  48. package/extensions/subagent/agents.ts +0 -762
  49. package/extensions/subagent/artifacts.ts +0 -101
  50. package/extensions/subagent/async-execution.ts +0 -521
  51. package/extensions/subagent/async-job-tracker.ts +0 -240
  52. package/extensions/subagent/async-status.ts +0 -242
  53. package/extensions/subagent/chain-clarify.ts +0 -1365
  54. package/extensions/subagent/chain-execution.ts +0 -854
  55. package/extensions/subagent/chain-serializer.ts +0 -127
  56. package/extensions/subagent/completion-dedupe.ts +0 -66
  57. package/extensions/subagent/doctor.ts +0 -201
  58. package/extensions/subagent/execution.ts +0 -748
  59. package/extensions/subagent/file-coalescer.ts +0 -43
  60. package/extensions/subagent/fork-context.ts +0 -64
  61. package/extensions/subagent/formatters.ts +0 -123
  62. package/extensions/subagent/frontmatter.ts +0 -32
  63. package/extensions/subagent/index.ts +0 -586
  64. package/extensions/subagent/intercom-bridge.ts +0 -241
  65. package/extensions/subagent/jsonl-writer.ts +0 -84
  66. package/extensions/subagent/model-fallback.ts +0 -109
  67. package/extensions/subagent/notify.ts +0 -111
  68. package/extensions/subagent/parallel-utils.ts +0 -109
  69. package/extensions/subagent/pi-args.ts +0 -139
  70. package/extensions/subagent/pi-spawn.ts +0 -101
  71. package/extensions/subagent/post-exit-stdio-guard.ts +0 -88
  72. package/extensions/subagent/prompt-template-bridge.ts +0 -400
  73. package/extensions/subagent/prompts/gather-context-and-clarify.md +0 -13
  74. package/extensions/subagent/prompts/parallel-cleanup.md +0 -42
  75. package/extensions/subagent/prompts/parallel-research.md +0 -50
  76. package/extensions/subagent/prompts/parallel-review.md +0 -40
  77. package/extensions/subagent/render-helpers.ts +0 -83
  78. package/extensions/subagent/render.ts +0 -908
  79. package/extensions/subagent/result-intercom.ts +0 -238
  80. package/extensions/subagent/result-watcher.ts +0 -172
  81. package/extensions/subagent/run-history.ts +0 -58
  82. package/extensions/subagent/run-status.ts +0 -137
  83. package/extensions/subagent/schemas.ts +0 -165
  84. package/extensions/subagent/session-tokens.ts +0 -51
  85. package/extensions/subagent/settings.ts +0 -368
  86. package/extensions/subagent/single-output.ts +0 -98
  87. package/extensions/subagent/skills.ts +0 -627
  88. package/extensions/subagent/slash-bridge.ts +0 -177
  89. package/extensions/subagent/slash-commands.ts +0 -304
  90. package/extensions/subagent/slash-live-state.ts +0 -295
  91. package/extensions/subagent/subagent-control.ts +0 -151
  92. package/extensions/subagent/subagent-executor.ts +0 -1915
  93. package/extensions/subagent/subagent-prompt-runtime.ts +0 -76
  94. package/extensions/subagent/subagent-runner.ts +0 -1471
  95. package/extensions/subagent/subagents-status.ts +0 -473
  96. package/extensions/subagent/text-editor.ts +0 -273
  97. package/extensions/subagent/throttle.ts +0 -77
  98. package/extensions/subagent/top-level-async.ts +0 -16
  99. package/extensions/subagent/types.ts +0 -624
  100. package/extensions/subagent/utils.ts +0 -457
  101. package/extensions/subagent/worktree.ts +0 -580
  102. package/extensions/super-pi-extension/agents/ce-oracle.md +0 -109
  103. package/extensions/super-pi-extension/agents/ce-planner.md +0 -116
  104. package/extensions/super-pi-extension/agents/ce-reviewer.md +0 -99
  105. package/extensions/super-pi-extension/agents/ce-scout.md +0 -80
  106. package/extensions/super-pi-extension/agents/ce-worker.md +0 -78
  107. package/extensions/super-pi-extension/chains/ce-parallel-review.chain.md +0 -34
  108. package/extensions/super-pi-extension/chains/ce-review-only.chain.md +0 -12
  109. package/extensions/super-pi-extension/chains/ce-standard.chain.md +0 -28
  110. package/extensions/super-pi-extension/index.ts +0 -129
  111. package/extensions/super-pi-extension/model-sync.ts +0 -204
  112. package/skills/08-status/SKILL.md +0 -41
  113. package/skills/08-status/references/artifact-locations.md +0 -10
  114. package/skills/09-help/SKILL.md +0 -37
  115. package/skills/09-help/references/workflow-sequence.md +0 -9
  116. package/skills/10-rules/SKILL.md +0 -76
  117. package/skills/pi-subagents/SKILL.md +0 -566
@@ -1,457 +0,0 @@
1
- // SPDX-FileCopyrightText: 2025 Nico Bailon
2
- // SPDX-License-Identifier: MIT
3
- // Source: https://github.com/nicobailon/pi-subagents
4
- /**
5
- * General utility functions for the subagent extension
6
- */
7
-
8
- import * as fs from "node:fs";
9
- import * as os from "node:os";
10
- import * as path from "node:path";
11
- import type { Message } from "@mariozechner/pi-ai";
12
- import { formatToolCall } from "./formatters.ts";
13
- import type { AgentProgress, AsyncStatus, Details, DisplayItem, ErrorInfo, SingleResult, ToolCallSummary } from "./types.ts";
14
-
15
- // ============================================================================
16
- // File System Utilities
17
- // ============================================================================
18
-
19
- const statusCache = new Map<string, { mtime: number; status: AsyncStatus }>();
20
-
21
- function getErrorMessage(error: unknown): string {
22
- return error instanceof Error ? error.message : String(error);
23
- }
24
-
25
- export function resolveChildCwd(baseCwd: string, childCwd: string | undefined): string {
26
- if (!childCwd) return baseCwd;
27
- return path.isAbsolute(childCwd) ? childCwd : path.resolve(baseCwd, childCwd);
28
- }
29
-
30
- function isNotFoundError(error: unknown): boolean {
31
- return typeof error === "object"
32
- && error !== null
33
- && "code" in error
34
- && (error as NodeJS.ErrnoException).code === "ENOENT";
35
- }
36
-
37
- /**
38
- * Read async job status from disk (with mtime-based caching)
39
- */
40
- export function readStatus(asyncDir: string): AsyncStatus | null {
41
- const statusPath = path.join(asyncDir, "status.json");
42
-
43
- let stat: fs.Stats;
44
- try {
45
- stat = fs.statSync(statusPath);
46
- } catch (error) {
47
- if (isNotFoundError(error)) return null;
48
- throw new Error(`Failed to inspect async status file '${statusPath}': ${getErrorMessage(error)}`, {
49
- cause: error instanceof Error ? error : undefined,
50
- });
51
- }
52
-
53
- const cached = statusCache.get(statusPath);
54
- if (cached && cached.mtime === stat.mtimeMs) {
55
- return cached.status;
56
- }
57
-
58
- let content: string;
59
- try {
60
- content = fs.readFileSync(statusPath, "utf-8");
61
- } catch (error) {
62
- if (isNotFoundError(error)) return null;
63
- throw new Error(`Failed to read async status file '${statusPath}': ${getErrorMessage(error)}`, {
64
- cause: error instanceof Error ? error : undefined,
65
- });
66
- }
67
-
68
- let status: AsyncStatus;
69
- try {
70
- status = JSON.parse(content) as AsyncStatus;
71
- } catch (error) {
72
- throw new Error(`Failed to parse async status file '${statusPath}': ${getErrorMessage(error)}`, {
73
- cause: error instanceof Error ? error : undefined,
74
- });
75
- }
76
-
77
- statusCache.set(statusPath, { mtime: stat.mtimeMs, status });
78
- if (statusCache.size > 50) {
79
- const firstKey = statusCache.keys().next().value;
80
- if (firstKey) statusCache.delete(firstKey);
81
- }
82
- return status;
83
- }
84
-
85
- const outputTailCache = new Map<string, { mtime: number; size: number; lines: string[] }>();
86
-
87
- /**
88
- * Get the last N lines from an output file (with mtime/size-based caching)
89
- */
90
- export function getOutputTail(outputFile: string | undefined, maxLines: number = 3): string[] {
91
- if (!outputFile) return [];
92
- let fd: number | null = null;
93
- try {
94
- const stat = fs.statSync(outputFile);
95
- if (stat.size === 0) return [];
96
-
97
- const cached = outputTailCache.get(outputFile);
98
- if (cached && cached.mtime === stat.mtimeMs && cached.size === stat.size) {
99
- return cached.lines;
100
- }
101
-
102
- const tailBytes = 4096;
103
- const start = Math.max(0, stat.size - tailBytes);
104
- fd = fs.openSync(outputFile, "r");
105
- const buffer = Buffer.alloc(Math.min(tailBytes, stat.size));
106
- fs.readSync(fd, buffer, 0, buffer.length, start);
107
- const content = buffer.toString("utf-8");
108
- const allLines = content.split("\n").filter((l) => l.trim());
109
- const lines = allLines.slice(-maxLines).map((l) => l.slice(0, 120) + (l.length > 120 ? "..." : ""));
110
-
111
- outputTailCache.set(outputFile, { mtime: stat.mtimeMs, size: stat.size, lines });
112
- if (outputTailCache.size > 20) {
113
- const firstKey = outputTailCache.keys().next().value;
114
- if (firstKey) outputTailCache.delete(firstKey);
115
- }
116
-
117
- return lines;
118
- } catch {
119
- // Output tails are UI-only hints; unreadable or missing files should render as no tail.
120
- return [];
121
- } finally {
122
- if (fd !== null) {
123
- try {
124
- fs.closeSync(fd);
125
- } catch {
126
- // Closing the best-effort tail file handle should not surface over the main status view.
127
- }
128
- }
129
- }
130
- }
131
-
132
- /**
133
- * Get human-readable last activity time for a file
134
- */
135
- export function getLastActivity(outputFile: string | undefined): string {
136
- if (!outputFile) return "";
137
- try {
138
- const stat = fs.statSync(outputFile);
139
- const ago = Date.now() - stat.mtimeMs;
140
- if (ago < 1000) return "active now";
141
- if (ago < 60000) return `active ${Math.floor(ago / 1000)}s ago`;
142
- return `active ${Math.floor(ago / 60000)}m ago`;
143
- } catch {
144
- // Last-activity text is best effort; missing files should simply omit the hint.
145
- return "";
146
- }
147
- }
148
-
149
- /**
150
- * Find a file/directory by prefix in a directory
151
- */
152
- export function findByPrefix(dir: string, prefix: string, suffix?: string): string | null {
153
- if (!fs.existsSync(dir)) return null;
154
- const entries = fs.readdirSync(dir).filter((entry) => entry.startsWith(prefix));
155
- if (suffix) {
156
- const withSuffix = entries.filter((entry) => entry.endsWith(suffix));
157
- return withSuffix.length > 0 ? path.join(dir, withSuffix.sort()[0]) : null;
158
- }
159
- if (entries.length === 0) return null;
160
- return path.join(dir, entries.sort()[0]);
161
- }
162
-
163
- /**
164
- * Find the latest session file in a directory
165
- */
166
- export function findLatestSessionFile(sessionDir: string): string | null {
167
- if (!fs.existsSync(sessionDir)) return null;
168
- const files = fs.readdirSync(sessionDir)
169
- .filter((f) => f.endsWith(".jsonl"))
170
- .map((f) => {
171
- const filePath = path.join(sessionDir, f);
172
- return {
173
- path: filePath,
174
- mtime: fs.statSync(filePath).mtimeMs,
175
- };
176
- })
177
- .sort((a, b) => b.mtime - a.mtime);
178
- return files.length > 0 ? files[0].path : null;
179
- }
180
-
181
- /**
182
- * Write a prompt to a temporary file
183
- */
184
- export function writePrompt(agent: string, prompt: string): { dir: string; path: string } {
185
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagent-"));
186
- const p = path.join(dir, `${agent.replace(/[^\w.-]/g, "_")}.md`);
187
- fs.writeFileSync(p, prompt, { mode: 0o600 });
188
- return { dir, path: p };
189
- }
190
-
191
- // ============================================================================
192
- // Message Parsing Utilities
193
- // ============================================================================
194
-
195
- /**
196
- * Get the final text output from a list of messages
197
- */
198
- export function getFinalOutput(messages: Message[]): string {
199
- for (let i = messages.length - 1; i >= 0; i--) {
200
- const msg = messages[i];
201
- if (msg.role === "assistant") {
202
- for (const part of msg.content) {
203
- if (part.type === "text") return part.text;
204
- }
205
- }
206
- }
207
- return "";
208
- }
209
-
210
- export function getSingleResultOutput(result: Pick<SingleResult, "finalOutput" | "messages">): string {
211
- return result.finalOutput ?? getFinalOutput(result.messages ?? []);
212
- }
213
-
214
- /**
215
- * Extract display items (text and tool calls) from messages
216
- */
217
- export function getDisplayItems(messages: Message[] | undefined): DisplayItem[] {
218
- if (!messages || messages.length === 0) return [];
219
- const items: DisplayItem[] = [];
220
- for (const msg of messages) {
221
- if (msg.role === "assistant") {
222
- for (const part of msg.content) {
223
- if (part.type === "text") items.push({ type: "text", text: part.text });
224
- else if (part.type === "toolCall") items.push({ type: "tool", name: part.name, args: part.arguments });
225
- }
226
- }
227
- }
228
- return items;
229
- }
230
-
231
- function compactCompletedProgress(progress: AgentProgress): AgentProgress {
232
- if (progress.status === "running") return progress;
233
- return {
234
- index: progress.index,
235
- agent: progress.agent,
236
- status: progress.status,
237
- activityState: progress.activityState,
238
- task: progress.task,
239
- skills: progress.skills,
240
- toolCount: progress.toolCount,
241
- tokens: progress.tokens,
242
- durationMs: progress.durationMs,
243
- error: progress.error,
244
- failedTool: progress.failedTool,
245
- recentTools: [],
246
- recentOutput: [],
247
- };
248
- }
249
-
250
- export function extractToolCallSummaries(messages: Message[] | undefined): ToolCallSummary[] {
251
- if (!messages?.length) return [];
252
- const summaries: ToolCallSummary[] = [];
253
- for (const msg of messages) {
254
- if (msg.role !== "assistant") continue;
255
- for (const part of msg.content) {
256
- if (part.type !== "toolCall") continue;
257
- const args = typeof part.arguments === "object" && part.arguments !== null && !Array.isArray(part.arguments)
258
- ? part.arguments
259
- : {};
260
- summaries.push({
261
- text: formatToolCall(part.name, args),
262
- expandedText: formatToolCall(part.name, args, true),
263
- });
264
- }
265
- }
266
- return summaries;
267
- }
268
-
269
- export function compactForegroundResult(result: SingleResult): SingleResult {
270
- if (result.progress?.status === "running") return result;
271
- const toolCalls = result.toolCalls?.length ? result.toolCalls : extractToolCallSummaries(result.messages);
272
- return {
273
- ...result,
274
- messages: undefined,
275
- progress: undefined,
276
- toolCalls: toolCalls.length ? toolCalls : undefined,
277
- };
278
- }
279
-
280
- export function compactForegroundDetails(details: Details): Details {
281
- return {
282
- ...details,
283
- results: details.results.map(compactForegroundResult),
284
- progress: details.progress
285
- ? details.progress.map(compactCompletedProgress)
286
- : undefined,
287
- };
288
- }
289
-
290
- /**
291
- * Detect errors in subagent execution from messages (only errors with no subsequent success)
292
- */
293
- export function detectSubagentError(messages: Message[]): ErrorInfo {
294
- let lastAssistantTextIndex = -1;
295
- for (let i = messages.length - 1; i >= 0; i--) {
296
- const msg = messages[i];
297
- if (msg.role === "assistant") {
298
- const hasText = Array.isArray(msg.content) && msg.content.some(
299
- (c) => c.type === "text" && "text" in c && typeof c.text === "string" && c.text.trim().length > 0,
300
- );
301
- if (hasText) {
302
- lastAssistantTextIndex = i;
303
- break;
304
- }
305
- }
306
- }
307
-
308
- const scanStart = lastAssistantTextIndex >= 0 ? lastAssistantTextIndex + 1 : 0;
309
-
310
- for (let i = messages.length - 1; i >= scanStart; i--) {
311
- const msg = messages[i];
312
- if (msg.role !== "toolResult") continue;
313
- const toolName = "toolName" in msg && typeof msg.toolName === "string" ? msg.toolName : undefined;
314
- const isError = "isError" in msg && msg.isError === true;
315
-
316
- if (isError) {
317
- const text = msg.content.find((c) => c.type === "text");
318
- const details = text && "text" in text ? text.text : undefined;
319
- const exitMatch = details?.match(/exit(?:ed)?\s*(?:with\s*)?(?:code|status)?\s*[:\s]?\s*(\d+)/i);
320
- return {
321
- hasError: true,
322
- exitCode: exitMatch ? parseInt(exitMatch[1], 10) : 1,
323
- errorType: toolName || "tool",
324
- details: details?.slice(0, 200),
325
- };
326
- }
327
-
328
- if (toolName !== "bash") continue;
329
-
330
- const text = msg.content.find((c) => c.type === "text");
331
- if (!text || !("text" in text)) continue;
332
- const output = text.text;
333
-
334
- const exitMatch = output.match(/exit(?:ed)?\s*(?:with\s*)?(?:code|status)?\s*[:\s]?\s*(\d+)/i);
335
- if (exitMatch) {
336
- const code = parseInt(exitMatch[1], 10);
337
- if (code !== 0) {
338
- return { hasError: true, exitCode: code, errorType: "bash", details: output.slice(0, 200) };
339
- }
340
- }
341
-
342
- // NOTE: These patterns can match legitimate output (grep results, logs,
343
- // testing). With the assistant-message check above, most false positives
344
- // are mitigated since the agent will have responded after routine errors.
345
- const fatalPatterns = [
346
- /command not found/i,
347
- /permission denied/i,
348
- /no such file or directory/i,
349
- /segmentation fault/i,
350
- /killed|terminated/i,
351
- /out of memory/i,
352
- /connection refused/i,
353
- /timeout/i,
354
- ];
355
- for (const pattern of fatalPatterns) {
356
- if (pattern.test(output)) {
357
- return { hasError: true, exitCode: 1, errorType: "bash", details: output.slice(0, 200) };
358
- }
359
- }
360
- }
361
-
362
- return { hasError: false };
363
- }
364
-
365
- /**
366
- * Extract a preview of tool arguments for display
367
- */
368
- export function extractToolArgsPreview(args: Record<string, unknown>): string {
369
- const truncatePreview = (value: string, maxLength: number): string =>
370
- value.length > maxLength ? `${value.slice(0, maxLength - 3)}...` : value;
371
-
372
- const stringifyPreviewValue = (value: unknown): string | undefined => {
373
- if (typeof value === "string" && value.trim().length > 0) return value;
374
- if (typeof value === "number" || typeof value === "boolean") return String(value);
375
- return undefined;
376
- };
377
-
378
- const previewArray = (value: unknown): string | undefined => {
379
- if (!Array.isArray(value) || value.length === 0) return undefined;
380
- const first = stringifyPreviewValue(value[0]);
381
- if (!first) return undefined;
382
- const suffix = value.length > 1 ? ` (+${value.length - 1} more)` : "";
383
- return `${first}${suffix}`;
384
- };
385
-
386
- // Handle MCP tool calls - show server/tool info
387
- if (args.tool && typeof args.tool === "string") {
388
- const server = args.server && typeof args.server === "string" ? `${args.server}/` : "";
389
- const toolArgs = args.args && typeof args.args === "string" ? ` ${args.args.slice(0, 40)}` : "";
390
- return `${server}${args.tool}${toolArgs}`;
391
- }
392
-
393
- const queriesPreview = previewArray(args.queries);
394
- if (queriesPreview) return truncatePreview(queriesPreview, 60);
395
- if (typeof args.query === "string" && args.query.trim().length > 0) return truncatePreview(args.query, 60);
396
- if (typeof args.workflow === "string" && args.workflow.trim().length > 0) return `workflow=${truncatePreview(args.workflow, 48)}`;
397
-
398
- if (typeof args.url === "string" && args.url.trim().length > 0) return truncatePreview(args.url, 60);
399
- const urlsPreview = previewArray(args.urls);
400
- if (urlsPreview) return truncatePreview(urlsPreview, 60);
401
- if (typeof args.prompt === "string" && args.prompt.trim().length > 0) return truncatePreview(args.prompt, 60);
402
-
403
- const previewKeys = ["command", "path", "file_path", "pattern", "query", "url", "task", "describe", "search"];
404
- for (const key of previewKeys) {
405
- if (args[key] && typeof args[key] === "string") {
406
- const value = args[key] as string;
407
- return truncatePreview(value, 60);
408
- }
409
- }
410
-
411
- // Fallback: show first string value found
412
- for (const [key, value] of Object.entries(args)) {
413
- const arrayPreview = previewArray(value);
414
- if (arrayPreview) return `${key}=${truncatePreview(arrayPreview, 50)}`;
415
- if (typeof value === "string" && value.length > 0) {
416
- const preview = truncatePreview(value, 50);
417
- return `${key}=${preview}`;
418
- }
419
- }
420
- return "";
421
- }
422
-
423
- /**
424
- * Extract text content from various message content formats
425
- */
426
- export function extractTextFromContent(content: unknown): string {
427
- if (!content) return "";
428
- // Handle string content directly
429
- if (typeof content === "string") return content;
430
- // Handle array content
431
- if (!Array.isArray(content)) return "";
432
- const texts: string[] = [];
433
- for (const part of content) {
434
- if (part && typeof part === "object") {
435
- // Handle { type: "text", text: "..." }
436
- if ("type" in part && part.type === "text" && "text" in part) {
437
- texts.push(String(part.text));
438
- }
439
- // Handle { type: "tool_result", content: "..." }
440
- else if ("type" in part && part.type === "tool_result" && "content" in part) {
441
- const inner = extractTextFromContent(part.content);
442
- if (inner) texts.push(inner);
443
- }
444
- // Handle { text: "..." } without type
445
- else if ("text" in part) {
446
- texts.push(String(part.text));
447
- }
448
- }
449
- }
450
- return texts.join("\n");
451
- }
452
-
453
- // ============================================================================
454
- // Concurrency Utilities
455
- // ============================================================================
456
-
457
- export { mapConcurrent } from "./parallel-utils.ts";