@ottocode/sdk 0.1.173

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 (125) hide show
  1. package/README.md +338 -0
  2. package/package.json +128 -0
  3. package/src/agent/types.ts +19 -0
  4. package/src/auth/src/copilot-oauth.ts +190 -0
  5. package/src/auth/src/index.ts +100 -0
  6. package/src/auth/src/oauth.ts +234 -0
  7. package/src/auth/src/openai-oauth.ts +394 -0
  8. package/src/auth/src/wallet.ts +51 -0
  9. package/src/browser.ts +32 -0
  10. package/src/config/src/index.ts +110 -0
  11. package/src/config/src/manager.ts +181 -0
  12. package/src/config/src/paths.ts +98 -0
  13. package/src/core/src/errors.ts +102 -0
  14. package/src/core/src/index.ts +108 -0
  15. package/src/core/src/providers/resolver.ts +244 -0
  16. package/src/core/src/streaming/artifacts.ts +41 -0
  17. package/src/core/src/terminals/bun-pty.ts +13 -0
  18. package/src/core/src/terminals/circular-buffer.ts +30 -0
  19. package/src/core/src/terminals/ensure-bun-pty.ts +70 -0
  20. package/src/core/src/terminals/index.ts +8 -0
  21. package/src/core/src/terminals/manager.ts +158 -0
  22. package/src/core/src/terminals/rust-libs.ts +30 -0
  23. package/src/core/src/terminals/terminal.ts +132 -0
  24. package/src/core/src/tools/bin-manager.ts +250 -0
  25. package/src/core/src/tools/builtin/bash.ts +155 -0
  26. package/src/core/src/tools/builtin/bash.txt +7 -0
  27. package/src/core/src/tools/builtin/file-cache.ts +39 -0
  28. package/src/core/src/tools/builtin/finish.ts +12 -0
  29. package/src/core/src/tools/builtin/finish.txt +10 -0
  30. package/src/core/src/tools/builtin/fs/cd.ts +19 -0
  31. package/src/core/src/tools/builtin/fs/cd.txt +5 -0
  32. package/src/core/src/tools/builtin/fs/index.ts +20 -0
  33. package/src/core/src/tools/builtin/fs/ls.ts +72 -0
  34. package/src/core/src/tools/builtin/fs/ls.txt +8 -0
  35. package/src/core/src/tools/builtin/fs/pwd.ts +17 -0
  36. package/src/core/src/tools/builtin/fs/pwd.txt +5 -0
  37. package/src/core/src/tools/builtin/fs/read.ts +119 -0
  38. package/src/core/src/tools/builtin/fs/read.txt +8 -0
  39. package/src/core/src/tools/builtin/fs/tree.ts +149 -0
  40. package/src/core/src/tools/builtin/fs/tree.txt +11 -0
  41. package/src/core/src/tools/builtin/fs/util.ts +95 -0
  42. package/src/core/src/tools/builtin/fs/write.ts +106 -0
  43. package/src/core/src/tools/builtin/fs/write.txt +11 -0
  44. package/src/core/src/tools/builtin/git.commit.txt +6 -0
  45. package/src/core/src/tools/builtin/git.diff.txt +5 -0
  46. package/src/core/src/tools/builtin/git.status.txt +5 -0
  47. package/src/core/src/tools/builtin/git.ts +151 -0
  48. package/src/core/src/tools/builtin/glob.ts +128 -0
  49. package/src/core/src/tools/builtin/glob.txt +10 -0
  50. package/src/core/src/tools/builtin/grep.ts +136 -0
  51. package/src/core/src/tools/builtin/grep.txt +9 -0
  52. package/src/core/src/tools/builtin/ignore.ts +45 -0
  53. package/src/core/src/tools/builtin/patch/apply.ts +546 -0
  54. package/src/core/src/tools/builtin/patch/constants.ts +5 -0
  55. package/src/core/src/tools/builtin/patch/normalize.ts +31 -0
  56. package/src/core/src/tools/builtin/patch/parse-enveloped.ts +209 -0
  57. package/src/core/src/tools/builtin/patch/parse-unified.ts +231 -0
  58. package/src/core/src/tools/builtin/patch/parse.ts +28 -0
  59. package/src/core/src/tools/builtin/patch/text.ts +23 -0
  60. package/src/core/src/tools/builtin/patch/types.ts +82 -0
  61. package/src/core/src/tools/builtin/patch.ts +167 -0
  62. package/src/core/src/tools/builtin/patch.txt +207 -0
  63. package/src/core/src/tools/builtin/progress.ts +55 -0
  64. package/src/core/src/tools/builtin/progress.txt +7 -0
  65. package/src/core/src/tools/builtin/ripgrep.ts +125 -0
  66. package/src/core/src/tools/builtin/ripgrep.txt +7 -0
  67. package/src/core/src/tools/builtin/terminal.ts +300 -0
  68. package/src/core/src/tools/builtin/terminal.txt +93 -0
  69. package/src/core/src/tools/builtin/todos.ts +66 -0
  70. package/src/core/src/tools/builtin/todos.txt +7 -0
  71. package/src/core/src/tools/builtin/websearch.ts +250 -0
  72. package/src/core/src/tools/builtin/websearch.txt +12 -0
  73. package/src/core/src/tools/error.ts +67 -0
  74. package/src/core/src/tools/loader.ts +421 -0
  75. package/src/core/src/types/index.ts +11 -0
  76. package/src/core/src/types/types.ts +4 -0
  77. package/src/core/src/utils/ansi.ts +27 -0
  78. package/src/core/src/utils/debug.ts +40 -0
  79. package/src/core/src/utils/logger.ts +150 -0
  80. package/src/index.ts +313 -0
  81. package/src/prompts/src/agents/build.txt +89 -0
  82. package/src/prompts/src/agents/general.txt +15 -0
  83. package/src/prompts/src/agents/plan.txt +10 -0
  84. package/src/prompts/src/agents/research.txt +50 -0
  85. package/src/prompts/src/base.txt +24 -0
  86. package/src/prompts/src/debug.ts +104 -0
  87. package/src/prompts/src/index.ts +1 -0
  88. package/src/prompts/src/modes/oneshot.txt +9 -0
  89. package/src/prompts/src/providers/anthropic.txt +247 -0
  90. package/src/prompts/src/providers/anthropicSpoof.txt +1 -0
  91. package/src/prompts/src/providers/default.txt +466 -0
  92. package/src/prompts/src/providers/google.txt +230 -0
  93. package/src/prompts/src/providers/moonshot.txt +24 -0
  94. package/src/prompts/src/providers/openai.txt +414 -0
  95. package/src/prompts/src/providers.ts +143 -0
  96. package/src/providers/src/anthropic-caching.ts +202 -0
  97. package/src/providers/src/anthropic-oauth-client.ts +157 -0
  98. package/src/providers/src/authorization.ts +17 -0
  99. package/src/providers/src/catalog-manual.ts +135 -0
  100. package/src/providers/src/catalog-merged.ts +9 -0
  101. package/src/providers/src/catalog.ts +8329 -0
  102. package/src/providers/src/copilot-client.ts +39 -0
  103. package/src/providers/src/env.ts +31 -0
  104. package/src/providers/src/google-client.ts +16 -0
  105. package/src/providers/src/index.ts +75 -0
  106. package/src/providers/src/moonshot-client.ts +25 -0
  107. package/src/providers/src/oauth-models.ts +39 -0
  108. package/src/providers/src/openai-oauth-client.ts +108 -0
  109. package/src/providers/src/opencode-client.ts +64 -0
  110. package/src/providers/src/openrouter-client.ts +31 -0
  111. package/src/providers/src/pricing.ts +178 -0
  112. package/src/providers/src/setu-client.ts +643 -0
  113. package/src/providers/src/utils.ts +210 -0
  114. package/src/providers/src/validate.ts +39 -0
  115. package/src/providers/src/zai-client.ts +47 -0
  116. package/src/skills/index.ts +34 -0
  117. package/src/skills/loader.ts +152 -0
  118. package/src/skills/parser.ts +108 -0
  119. package/src/skills/tool.ts +87 -0
  120. package/src/skills/types.ts +41 -0
  121. package/src/skills/validator.ts +110 -0
  122. package/src/types/src/auth.ts +33 -0
  123. package/src/types/src/config.ts +36 -0
  124. package/src/types/src/index.ts +20 -0
  125. package/src/types/src/provider.ts +71 -0
@@ -0,0 +1,244 @@
1
+ import { openai, createOpenAI } from '@ai-sdk/openai';
2
+ import { anthropic, createAnthropic } from '@ai-sdk/anthropic';
3
+ import { google, createGoogleGenerativeAI } from '@ai-sdk/google';
4
+ import { createOpenRouter } from '@openrouter/ai-sdk-provider';
5
+ import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
6
+ import {
7
+ catalog,
8
+ createSetuModel,
9
+ createOpenAIOAuthModel,
10
+ } from '../../../providers/src/index.ts';
11
+ import { createCopilotModel } from '../../../providers/src/copilot-client.ts';
12
+ import type { OAuth } from '../../../types/src/index.ts';
13
+
14
+ function needsResponsesApi(model: string): boolean {
15
+ const m = model.toLowerCase();
16
+ if (m.includes('gpt-5')) return true;
17
+ if (m.startsWith('o1')) return true;
18
+ if (m.startsWith('o3')) return true;
19
+ if (m.startsWith('o4')) return true;
20
+ if (m.includes('codex-mini')) return true;
21
+ return false;
22
+ }
23
+
24
+ function resolveOpenAIModel(
25
+ instance: ReturnType<typeof createOpenAI>,
26
+ model: string,
27
+ ) {
28
+ return needsResponsesApi(model) ? instance.responses(model) : instance(model);
29
+ }
30
+
31
+ export type ProviderName =
32
+ | 'openai'
33
+ | 'anthropic'
34
+ | 'google'
35
+ | 'openrouter'
36
+ | 'opencode'
37
+ | 'copilot'
38
+ | 'setu'
39
+ | 'zai'
40
+ | 'zai-coding'
41
+ | 'moonshot';
42
+
43
+ export type ModelConfig = {
44
+ apiKey?: string;
45
+ customFetch?: typeof fetch;
46
+ baseURL?: string;
47
+ oauth?: OAuth;
48
+ projectRoot?: string;
49
+ };
50
+
51
+ export async function resolveModel(
52
+ provider: ProviderName,
53
+ model: string,
54
+ config: ModelConfig = {},
55
+ ) {
56
+ if (provider === 'openai') {
57
+ if (config.oauth) {
58
+ return createOpenAIOAuthModel(model, {
59
+ oauth: config.oauth,
60
+ projectRoot: config.projectRoot,
61
+ });
62
+ }
63
+ if (config.customFetch) {
64
+ const instance = createOpenAI({
65
+ apiKey: config.apiKey || 'oauth-token',
66
+ fetch: config.customFetch,
67
+ });
68
+ return resolveOpenAIModel(instance, model);
69
+ }
70
+ if (config.apiKey) {
71
+ const instance = createOpenAI({ apiKey: config.apiKey });
72
+ return resolveOpenAIModel(instance, model);
73
+ }
74
+ return needsResponsesApi(model) ? openai.responses(model) : openai(model);
75
+ }
76
+
77
+ if (provider === 'anthropic') {
78
+ if (config.customFetch) {
79
+ return createAnthropic({
80
+ apiKey: config.apiKey || '',
81
+ fetch: config.customFetch as typeof fetch,
82
+ });
83
+ }
84
+ if (config.apiKey) {
85
+ const instance = createAnthropic({ apiKey: config.apiKey });
86
+ return instance(model);
87
+ }
88
+ return anthropic(model);
89
+ }
90
+
91
+ if (provider === 'google') {
92
+ if (config.apiKey) {
93
+ const instance = createGoogleGenerativeAI({ apiKey: config.apiKey });
94
+ return instance(model);
95
+ }
96
+ return google(model);
97
+ }
98
+
99
+ if (provider === 'openrouter') {
100
+ const apiKey = config.apiKey || process.env.OPENROUTER_API_KEY || '';
101
+ const openrouter = createOpenRouter({ apiKey });
102
+ return openrouter.chat(model);
103
+ }
104
+
105
+ if (provider === 'opencode') {
106
+ const entry = catalog[provider];
107
+ const normalizedModel = normalizeModelIdentifier(provider, model);
108
+ const modelInfo =
109
+ entry?.models.find((m) => m.id === normalizedModel) ??
110
+ entry?.models.find((m) => m.id === model);
111
+ const resolvedModelId = modelInfo?.id ?? normalizedModel ?? model;
112
+ const binding = modelInfo?.provider?.npm ?? entry?.npm;
113
+ const apiKey = config.apiKey || process.env.OPENCODE_API_KEY || '';
114
+ const baseURL =
115
+ config.baseURL ||
116
+ modelInfo?.provider?.baseURL ||
117
+ modelInfo?.provider?.api ||
118
+ entry?.api ||
119
+ 'https://opencode.ai/zen/v1';
120
+ const headers = apiKey ? { Authorization: `Bearer ${apiKey}` } : undefined;
121
+ if (binding === '@ai-sdk/openai') {
122
+ const instance = createOpenAI({ apiKey, baseURL });
123
+ return instance(resolvedModelId);
124
+ }
125
+ if (binding === '@ai-sdk/anthropic') {
126
+ const instance = createAnthropic({ apiKey, baseURL });
127
+ return instance(resolvedModelId);
128
+ }
129
+ if (binding === '@ai-sdk/openai-compatible') {
130
+ const instance = createOpenAICompatible({
131
+ name: entry?.label ?? 'opencode',
132
+ baseURL,
133
+ headers,
134
+ });
135
+ return instance(resolvedModelId);
136
+ }
137
+
138
+ const ocOpenAI = createOpenAI({ apiKey, baseURL });
139
+ const ocAnthropic = createAnthropic({ apiKey, baseURL });
140
+ const ocCompat = createOpenAICompatible({
141
+ name: entry?.label ?? 'opencode',
142
+ baseURL,
143
+ headers,
144
+ });
145
+
146
+ const id = resolvedModelId.toLowerCase();
147
+ if (id.includes('claude')) return ocAnthropic(resolvedModelId);
148
+ if (
149
+ id.includes('qwen3-coder') ||
150
+ id.includes('grok-code') ||
151
+ id.includes('kimi-k2')
152
+ )
153
+ return ocCompat(resolvedModelId);
154
+ return ocOpenAI(resolvedModelId);
155
+ }
156
+
157
+ if (provider === 'copilot') {
158
+ if (config.oauth) {
159
+ return createCopilotModel(model, { oauth: config.oauth });
160
+ }
161
+ throw new Error(
162
+ 'Copilot provider requires OAuth. Run `otto auth login copilot`.',
163
+ );
164
+ }
165
+
166
+ if (provider === 'setu') {
167
+ const privateKey = config.apiKey || process.env.SETU_PRIVATE_KEY || '';
168
+ if (!privateKey) {
169
+ throw new Error(
170
+ 'Setu provider requires SETU_PRIVATE_KEY (base58 Solana secret).',
171
+ );
172
+ }
173
+ const baseURL = config.baseURL || process.env.SETU_BASE_URL;
174
+ const rpcURL = process.env.SETU_SOLANA_RPC_URL;
175
+ return createSetuModel(
176
+ model,
177
+ { privateKey },
178
+ {
179
+ baseURL,
180
+ rpcURL,
181
+ },
182
+ );
183
+ }
184
+
185
+ if (provider === 'zai') {
186
+ const entry = catalog[provider];
187
+ const apiKey =
188
+ config.apiKey ||
189
+ process.env.ZAI_API_KEY ||
190
+ process.env.ZHIPU_API_KEY ||
191
+ '';
192
+ const baseURL =
193
+ config.baseURL || entry?.api || 'https://api.z.ai/api/paas/v4';
194
+ const headers = apiKey ? { Authorization: `Bearer ${apiKey}` } : undefined;
195
+ const instance = createOpenAICompatible({
196
+ name: entry?.label ?? 'Z.AI',
197
+ baseURL,
198
+ headers,
199
+ });
200
+ return instance(model);
201
+ }
202
+
203
+ if (provider === 'zai-coding') {
204
+ const entry = catalog[provider];
205
+ const apiKey =
206
+ config.apiKey ||
207
+ process.env.ZAI_API_KEY ||
208
+ process.env.ZHIPU_API_KEY ||
209
+ '';
210
+ const baseURL =
211
+ config.baseURL || entry?.api || 'https://api.z.ai/api/coding/paas/v4';
212
+ const headers = apiKey ? { Authorization: `Bearer ${apiKey}` } : undefined;
213
+ const instance = createOpenAICompatible({
214
+ name: entry?.label ?? 'Z.AI Coding',
215
+ baseURL,
216
+ headers,
217
+ });
218
+ return instance(model);
219
+ }
220
+
221
+ if (provider === 'moonshot') {
222
+ const entry = catalog[provider];
223
+ const apiKey = config.apiKey || process.env.MOONSHOT_API_KEY || '';
224
+ const baseURL =
225
+ config.baseURL || entry?.api || 'https://api.moonshot.ai/v1';
226
+ const headers = apiKey ? { Authorization: `Bearer ${apiKey}` } : undefined;
227
+ const instance = createOpenAICompatible({
228
+ name: entry?.label ?? 'Moonshot AI',
229
+ baseURL,
230
+ headers,
231
+ });
232
+ return instance(model);
233
+ }
234
+
235
+ throw new Error(`Unsupported provider: ${provider}`);
236
+ }
237
+
238
+ function normalizeModelIdentifier(
239
+ provider: ProviderName,
240
+ model: string,
241
+ ): string {
242
+ const prefix = `${provider}/`;
243
+ return model.startsWith(prefix) ? model.slice(prefix.length) : model;
244
+ }
@@ -0,0 +1,41 @@
1
+ // Shared types and helpers for tool artifacts persisted in message parts.
2
+
3
+ export type FileDiffArtifact = {
4
+ kind: 'file_diff';
5
+ patchFormat: 'unified';
6
+ patch: string; // full unified patch text
7
+ summary?: { files?: number; additions?: number; deletions?: number };
8
+ };
9
+
10
+ export type FileArtifact = {
11
+ kind: 'file';
12
+ path: string; // repository-relative path
13
+ mime?: string;
14
+ size?: number;
15
+ sha256?: string;
16
+ };
17
+
18
+ export type Artifact =
19
+ | FileDiffArtifact
20
+ | FileArtifact
21
+ | { kind: string; [k: string]: unknown };
22
+
23
+ export function createFileDiffArtifact(
24
+ patch: string,
25
+ summary?: { files?: number; additions?: number; deletions?: number },
26
+ ): FileDiffArtifact {
27
+ return { kind: 'file_diff', patchFormat: 'unified', patch, summary };
28
+ }
29
+
30
+ export function createToolResultPayload(
31
+ name: string,
32
+ result?: unknown,
33
+ artifact?: Artifact,
34
+ ) {
35
+ const payload: { name: string; result?: unknown; artifact?: Artifact } = {
36
+ name,
37
+ };
38
+ if (result !== undefined) payload.result = result;
39
+ if (artifact) payload.artifact = artifact;
40
+ return payload;
41
+ }
@@ -0,0 +1,13 @@
1
+ import { ensureBunPtyLibrary } from './ensure-bun-pty.ts';
2
+
3
+ await ensureBunPtyLibrary();
4
+
5
+ const bunPty = await import('bun-pty');
6
+
7
+ export const spawn = bunPty.spawn;
8
+
9
+ export type {
10
+ IPty,
11
+ IPtyForkOptions as PtyOptions,
12
+ IExitEvent,
13
+ } from 'bun-pty';
@@ -0,0 +1,30 @@
1
+ export class CircularBuffer {
2
+ private buffer: string[] = [];
3
+ private maxSize: number;
4
+
5
+ constructor(maxSize = 500) {
6
+ this.maxSize = maxSize;
7
+ }
8
+
9
+ push(line: string): void {
10
+ this.buffer.push(line);
11
+ if (this.buffer.length > this.maxSize) {
12
+ this.buffer.shift();
13
+ }
14
+ }
15
+
16
+ read(lines?: number): string[] {
17
+ if (lines === undefined) {
18
+ return [...this.buffer];
19
+ }
20
+ return this.buffer.slice(-lines);
21
+ }
22
+
23
+ clear(): void {
24
+ this.buffer = [];
25
+ }
26
+
27
+ get length(): number {
28
+ return this.buffer.length;
29
+ }
30
+ }
@@ -0,0 +1,70 @@
1
+ import { existsSync, mkdirSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { getGlobalConfigDir } from '../../../../src/config/src/paths.ts';
4
+ import { RUST_PTY_LIBS } from './rust-libs.ts';
5
+
6
+ function resolveLibraryFilename(): string {
7
+ const platform = process.platform;
8
+ const arch = process.arch;
9
+
10
+ if (platform === 'darwin') {
11
+ return arch === 'arm64' ? 'librust_pty_arm64.dylib' : 'librust_pty.dylib';
12
+ }
13
+
14
+ if (platform === 'win32') {
15
+ return 'rust_pty.dll';
16
+ }
17
+
18
+ return arch === 'arm64' ? 'librust_pty_arm64.so' : 'librust_pty.so';
19
+ }
20
+
21
+ function tryUseExistingPath(path?: string | null): string | null {
22
+ if (!path) return null;
23
+ if (existsSync(path)) {
24
+ process.env.BUN_PTY_LIB = path;
25
+ return path;
26
+ }
27
+ return null;
28
+ }
29
+
30
+ export async function ensureBunPtyLibrary(): Promise<string | null> {
31
+ const already = tryUseExistingPath(process.env.BUN_PTY_LIB);
32
+ if (already) return already;
33
+
34
+ const filename = resolveLibraryFilename();
35
+ const candidates: string[] = [];
36
+
37
+ candidates.push(
38
+ join(
39
+ process.cwd(),
40
+ 'node_modules',
41
+ 'bun-pty',
42
+ 'rust-pty',
43
+ 'target',
44
+ 'release',
45
+ filename,
46
+ ),
47
+ );
48
+
49
+ for (const candidate of candidates) {
50
+ const path = tryUseExistingPath(candidate);
51
+ if (path) return path;
52
+ }
53
+
54
+ const platformLibs =
55
+ RUST_PTY_LIBS[process.platform as keyof typeof RUST_PTY_LIBS];
56
+ const embeddedPath =
57
+ platformLibs?.[process.arch === 'arm64' ? 'arm64' : 'x64'];
58
+
59
+ if (embeddedPath) {
60
+ const targetDir = join(getGlobalConfigDir(), 'runtime', 'bun-pty');
61
+ mkdirSync(targetDir, { recursive: true });
62
+ const targetPath = join(targetDir, filename);
63
+ const source = Bun.file(embeddedPath);
64
+ await Bun.write(targetPath, source);
65
+ process.env.BUN_PTY_LIB = targetPath;
66
+ return targetPath;
67
+ }
68
+
69
+ return null;
70
+ }
@@ -0,0 +1,8 @@
1
+ export { TerminalManager, type CreateTerminalOptions } from './manager.ts';
2
+ export {
3
+ Terminal,
4
+ type TerminalOptions,
5
+ type TerminalStatus,
6
+ type TerminalCreator,
7
+ } from './terminal.ts';
8
+ export { CircularBuffer } from './circular-buffer.ts';
@@ -0,0 +1,158 @@
1
+ import { randomBytes } from 'node:crypto';
2
+ import type { PtyOptions } from './bun-pty.ts';
3
+ import { spawn as spawnPty } from './bun-pty.ts';
4
+ import { Terminal } from './terminal.ts';
5
+ import { logger } from '../utils/logger.ts';
6
+ import { getAugmentedPath } from '../tools/bin-manager.ts';
7
+
8
+ const MAX_TERMINALS = 10;
9
+ const CLEANUP_DELAY_MS = 5 * 60 * 1000;
10
+
11
+ export interface CreateTerminalOptions {
12
+ command: string;
13
+ args?: string[];
14
+ cwd: string;
15
+ purpose: string;
16
+ createdBy: 'user' | 'llm';
17
+ title?: string;
18
+ }
19
+
20
+ export class TerminalManager {
21
+ private terminals = new Map<string, Terminal>();
22
+ private cleanupTimers = new Map<string, NodeJS.Timeout>();
23
+
24
+ create(options: CreateTerminalOptions): Terminal {
25
+ if (this.terminals.size >= MAX_TERMINALS) {
26
+ throw new Error(`Maximum ${MAX_TERMINALS} terminals reached`);
27
+ }
28
+
29
+ const id = this.generateId();
30
+
31
+ try {
32
+ logger.debug('TerminalManager: creating terminal', {
33
+ id,
34
+ command: options.command,
35
+ args: options.args,
36
+ cwd: options.cwd,
37
+ purpose: options.purpose,
38
+ });
39
+
40
+ const ptyOptions: PtyOptions = {
41
+ name: 'xterm-256color',
42
+ cols: 80,
43
+ rows: 30,
44
+ cwd: options.cwd,
45
+ env: {
46
+ ...process.env,
47
+ TERM: 'xterm-256color',
48
+ PATH: getAugmentedPath(),
49
+ PROMPT_EOL_MARK: '',
50
+ } as Record<string, string>,
51
+ };
52
+
53
+ const pty = spawnPty(options.command, options.args || [], ptyOptions);
54
+
55
+ logger.debug('TerminalManager: PTY created', {
56
+ pid: pty.pid,
57
+ });
58
+
59
+ const terminal = new Terminal(id, pty, options);
60
+
61
+ terminal.onExit((_exitCode) => {
62
+ const timer = setTimeout(() => {
63
+ this.delete(id);
64
+ }, CLEANUP_DELAY_MS);
65
+
66
+ this.cleanupTimers.set(id, timer);
67
+ });
68
+
69
+ this.terminals.set(id, terminal);
70
+
71
+ logger.debug('TerminalManager: terminal added to map', { id });
72
+
73
+ return terminal;
74
+ } catch (error) {
75
+ logger.error('TerminalManager: failed to create terminal', error);
76
+ throw error;
77
+ }
78
+ }
79
+
80
+ get(id: string): Terminal | undefined {
81
+ return this.terminals.get(id);
82
+ }
83
+
84
+ list(): Terminal[] {
85
+ return Array.from(this.terminals.values());
86
+ }
87
+
88
+ async kill(id: string): Promise<void> {
89
+ const terminal = this.terminals.get(id);
90
+ if (!terminal) {
91
+ throw new Error(`Terminal ${id} not found`);
92
+ }
93
+
94
+ terminal.kill();
95
+
96
+ await new Promise<void>((resolve) => {
97
+ if (terminal.status === 'exited') {
98
+ resolve();
99
+ return;
100
+ }
101
+
102
+ const exitHandler = () => {
103
+ terminal.removeExitListener(exitHandler);
104
+ resolve();
105
+ };
106
+
107
+ terminal.onExit(exitHandler);
108
+
109
+ setTimeout(() => {
110
+ terminal.removeExitListener(exitHandler);
111
+ resolve();
112
+ }, 5000);
113
+ });
114
+
115
+ this.delete(id);
116
+ }
117
+
118
+ async killAll(): Promise<void> {
119
+ const killPromises = Array.from(this.terminals.keys()).map((id) =>
120
+ this.kill(id).catch((err) =>
121
+ logger.error(`Failed to kill terminal ${id}`, err),
122
+ ),
123
+ );
124
+
125
+ await Promise.all(killPromises);
126
+ }
127
+
128
+ delete(id: string): boolean {
129
+ const timer = this.cleanupTimers.get(id);
130
+ if (timer) {
131
+ clearTimeout(timer);
132
+ this.cleanupTimers.delete(id);
133
+ }
134
+
135
+ return this.terminals.delete(id);
136
+ }
137
+
138
+ private generateId(): string {
139
+ return `term-${randomBytes(8).toString('hex')}`;
140
+ }
141
+
142
+ getContext(): string {
143
+ const terminals = this.list();
144
+
145
+ if (terminals.length === 0) {
146
+ return '';
147
+ }
148
+
149
+ const summary = terminals
150
+ .map(
151
+ (t) =>
152
+ `- [${t.id}] ${t.purpose} (${t.status}, ${t.createdBy}, pid: ${t.pid})`,
153
+ )
154
+ .join('\n');
155
+
156
+ return `\n\n## Active Terminals (${terminals.length}):\n${summary}\n\nYou can read from any terminal using the 'terminal' tool with operation: 'read'.`;
157
+ }
158
+ }
@@ -0,0 +1,30 @@
1
+ import darwinArm64 from 'bun-pty/rust-pty/target/release/librust_pty_arm64.dylib' with {
2
+ type: 'file',
3
+ };
4
+ import darwinX64 from 'bun-pty/rust-pty/target/release/librust_pty.dylib' with {
5
+ type: 'file',
6
+ };
7
+ import linuxArm64 from 'bun-pty/rust-pty/target/release/librust_pty_arm64.so' with {
8
+ type: 'file',
9
+ };
10
+ import linuxX64 from 'bun-pty/rust-pty/target/release/librust_pty.so' with {
11
+ type: 'file',
12
+ };
13
+ import windowsDll from 'bun-pty/rust-pty/target/release/rust_pty.dll' with {
14
+ type: 'file',
15
+ };
16
+
17
+ export const RUST_PTY_LIBS = {
18
+ darwin: {
19
+ arm64: darwinArm64,
20
+ x64: darwinX64,
21
+ },
22
+ linux: {
23
+ arm64: linuxArm64,
24
+ x64: linuxX64,
25
+ },
26
+ win32: {
27
+ arm64: windowsDll,
28
+ x64: windowsDll,
29
+ },
30
+ } as const;