wraptc 1.0.2 → 1.0.4

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 (71) hide show
  1. package/bin/wraptc +4 -4
  2. package/package.json +2 -2
  3. package/src/cli/__tests__/cli.test.ts +337 -0
  4. package/src/cli/index.ts +149 -0
  5. package/src/core/__tests__/fixtures/configs/project-config.json +14 -0
  6. package/src/core/__tests__/fixtures/configs/system-config.json +14 -0
  7. package/src/core/__tests__/fixtures/configs/user-config.json +15 -0
  8. package/src/core/__tests__/integration/integration.test.ts +241 -0
  9. package/src/core/__tests__/integration/mock-coder-adapter.test.ts +243 -0
  10. package/src/core/__tests__/test-utils.ts +136 -0
  11. package/src/core/__tests__/unit/adapters/runner.test.ts +302 -0
  12. package/src/core/__tests__/unit/basic-test.test.ts +44 -0
  13. package/src/core/__tests__/unit/basic.test.ts +12 -0
  14. package/src/core/__tests__/unit/config.test.ts +244 -0
  15. package/src/core/__tests__/unit/error-patterns.test.ts +181 -0
  16. package/src/core/__tests__/unit/memory-monitor.test.ts +354 -0
  17. package/src/core/__tests__/unit/plugin/registry.test.ts +356 -0
  18. package/src/core/__tests__/unit/providers/codex.test.ts +173 -0
  19. package/src/core/__tests__/unit/providers/configurable.test.ts +429 -0
  20. package/src/core/__tests__/unit/providers/gemini.test.ts +251 -0
  21. package/src/core/__tests__/unit/providers/opencode.test.ts +258 -0
  22. package/src/core/__tests__/unit/providers/qwen-code.test.ts +195 -0
  23. package/src/core/__tests__/unit/providers/simple-codex.test.ts +18 -0
  24. package/src/core/__tests__/unit/router.test.ts +967 -0
  25. package/src/core/__tests__/unit/state.test.ts +1079 -0
  26. package/src/core/__tests__/unit/unified/capabilities.test.ts +186 -0
  27. package/src/core/__tests__/unit/wrap-terminalcoder.test.ts +32 -0
  28. package/src/core/adapters/builtin/codex.ts +35 -0
  29. package/src/core/adapters/builtin/gemini.ts +34 -0
  30. package/src/core/adapters/builtin/index.ts +31 -0
  31. package/src/core/adapters/builtin/mock-coder.ts +148 -0
  32. package/src/core/adapters/builtin/qwen.ts +34 -0
  33. package/src/core/adapters/define.ts +48 -0
  34. package/src/core/adapters/index.ts +43 -0
  35. package/src/core/adapters/loader.ts +143 -0
  36. package/src/core/adapters/provider-bridge.ts +190 -0
  37. package/src/core/adapters/runner.ts +437 -0
  38. package/src/core/adapters/types.ts +172 -0
  39. package/src/core/config.ts +290 -0
  40. package/src/core/define-provider.ts +212 -0
  41. package/src/core/error-patterns.ts +147 -0
  42. package/src/core/index.ts +130 -0
  43. package/src/core/memory-monitor.ts +171 -0
  44. package/src/core/plugin/builtin.ts +87 -0
  45. package/src/core/plugin/index.ts +34 -0
  46. package/src/core/plugin/registry.ts +350 -0
  47. package/src/core/plugin/types.ts +209 -0
  48. package/src/core/provider-factory.ts +397 -0
  49. package/src/core/provider-loader.ts +171 -0
  50. package/src/core/providers/codex.ts +56 -0
  51. package/src/core/providers/configurable.ts +637 -0
  52. package/src/core/providers/custom.ts +261 -0
  53. package/src/core/providers/gemini.ts +41 -0
  54. package/src/core/providers/index.ts +383 -0
  55. package/src/core/providers/opencode.ts +168 -0
  56. package/src/core/providers/qwen-code.ts +41 -0
  57. package/src/core/router.ts +370 -0
  58. package/src/core/state.ts +258 -0
  59. package/src/core/types.ts +206 -0
  60. package/src/core/unified/capabilities.ts +184 -0
  61. package/src/core/unified/errors.ts +141 -0
  62. package/src/core/unified/index.ts +29 -0
  63. package/src/core/unified/output.ts +189 -0
  64. package/src/core/wrap-terminalcoder.ts +245 -0
  65. package/src/mcp/__tests__/server.test.ts +295 -0
  66. package/src/mcp/server.ts +284 -0
  67. package/src/test-fixtures/mock-coder.sh +194 -0
  68. package/dist/cli/index.js +0 -16501
  69. package/dist/core/index.js +0 -7531
  70. package/dist/mcp/server.js +0 -14568
  71. package/dist/wraptc-1.0.2.tgz +0 -0
@@ -0,0 +1,143 @@
1
+ /**
2
+ * AdapterLoader - Load adapter definitions from files
3
+ *
4
+ * Supports loading .ts adapter files from:
5
+ * - ~/.config/wraptc/adapters/
6
+ * - ./.wtc/adapters/
7
+ */
8
+
9
+ import { existsSync } from "node:fs";
10
+ import { join } from "node:path";
11
+ import { builtInAdapters } from "./builtin";
12
+ import type { AdapterDefinition } from "./types";
13
+
14
+ /**
15
+ * Directories to scan for user adapters (in priority order)
16
+ */
17
+ const ADAPTER_DIRS = [
18
+ // User adapters (highest priority)
19
+ join(process.env.HOME ?? "~", ".config", "wrap-terminalcoder", "adapters"),
20
+ // Project-local adapters
21
+ "./.wtc/adapters",
22
+ ];
23
+
24
+ /**
25
+ * Load a single adapter from a file
26
+ */
27
+ async function loadAdapterFile(filePath: string): Promise<AdapterDefinition | null> {
28
+ try {
29
+ // Only support .ts files (TypeScript only approach)
30
+ if (!filePath.endsWith(".ts") && !filePath.endsWith(".js")) {
31
+ return null;
32
+ }
33
+
34
+ // Skip test files and declaration files
35
+ if (filePath.includes(".test.") || filePath.endsWith(".d.ts")) {
36
+ return null;
37
+ }
38
+
39
+ // Dynamic import the adapter
40
+ const module = await import(filePath);
41
+ const adapter = module.default as AdapterDefinition;
42
+
43
+ // Validate minimal requirements
44
+ if (!adapter || !adapter.id || !adapter.binary) {
45
+ console.warn(`Invalid adapter at ${filePath}: missing id or binary`);
46
+ return null;
47
+ }
48
+
49
+ return adapter;
50
+ } catch (err) {
51
+ console.warn(`Failed to load adapter from ${filePath}:`, err);
52
+ return null;
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Load all adapters from a directory
58
+ */
59
+ async function loadAdaptersFromDir(dir: string): Promise<Map<string, AdapterDefinition>> {
60
+ const adapters = new Map<string, AdapterDefinition>();
61
+
62
+ const resolvedDir = dir.startsWith(".") ? join(process.cwd(), dir) : dir;
63
+
64
+ if (!existsSync(resolvedDir)) {
65
+ return adapters;
66
+ }
67
+
68
+ try {
69
+ const glob = new Bun.Glob("*.ts");
70
+
71
+ for await (const file of glob.scan(resolvedDir)) {
72
+ const filePath = join(resolvedDir, file);
73
+ const adapter = await loadAdapterFile(filePath);
74
+
75
+ if (adapter) {
76
+ adapters.set(adapter.id, adapter);
77
+ }
78
+ }
79
+ } catch (err) {
80
+ console.warn(`Failed to scan adapter directory ${resolvedDir}:`, err);
81
+ }
82
+
83
+ return adapters;
84
+ }
85
+
86
+ /**
87
+ * Load all user adapters from configured directories
88
+ */
89
+ export async function loadUserAdapters(): Promise<Map<string, AdapterDefinition>> {
90
+ const adapters = new Map<string, AdapterDefinition>();
91
+
92
+ // Load from each directory (later directories override earlier ones)
93
+ for (const dir of ADAPTER_DIRS) {
94
+ const dirAdapters = await loadAdaptersFromDir(dir);
95
+ for (const [id, adapter] of dirAdapters) {
96
+ adapters.set(id, adapter);
97
+ }
98
+ }
99
+
100
+ return adapters;
101
+ }
102
+
103
+ /**
104
+ * Load all adapters (built-in + user)
105
+ *
106
+ * User adapters override built-in adapters with the same ID
107
+ */
108
+ export async function loadAllAdapters(): Promise<Map<string, AdapterDefinition>> {
109
+ const adapters = new Map<string, AdapterDefinition>();
110
+
111
+ // Add built-in adapters first
112
+ for (const adapter of builtInAdapters) {
113
+ adapters.set(adapter.id, adapter);
114
+ }
115
+
116
+ // Load user adapters (override built-in)
117
+ const userAdapters = await loadUserAdapters();
118
+ for (const [id, adapter] of userAdapters) {
119
+ adapters.set(id, adapter);
120
+ }
121
+
122
+ return adapters;
123
+ }
124
+
125
+ /**
126
+ * Get adapter directories being scanned
127
+ */
128
+ export function getAdapterDirectories(): string[] {
129
+ return ADAPTER_DIRS.map((dir) => (dir.startsWith(".") ? join(process.cwd(), dir) : dir));
130
+ }
131
+
132
+ /**
133
+ * Ensure user adapter directory exists
134
+ */
135
+ export async function ensureUserAdapterDir(): Promise<string> {
136
+ const userDir = join(process.env.HOME ?? "~", ".config", "wrap-terminalcoder", "adapters");
137
+
138
+ if (!existsSync(userDir)) {
139
+ await Bun.$`mkdir -p ${userDir}`.quiet();
140
+ }
141
+
142
+ return userDir;
143
+ }
@@ -0,0 +1,190 @@
1
+ /**
2
+ * AdapterProviderBridge - Wrap adapters as Provider interface
3
+ *
4
+ * Enables adapters to work with the existing Router and ProviderFactory
5
+ * for backward compatibility.
6
+ */
7
+
8
+ import type { Provider, ProviderInvokeOptions } from "../providers/index";
9
+ import type {
10
+ CodingEvent,
11
+ CodingRequest,
12
+ ProviderErrorContext,
13
+ ProviderErrorKind,
14
+ ProviderInfo,
15
+ } from "../types";
16
+ import { AdapterError, AdapterRunner } from "./runner";
17
+ import type { AdapterDefinition } from "./types";
18
+
19
+ /**
20
+ * Bridge that wraps an AdapterDefinition as a Provider
21
+ */
22
+ export class AdapterProviderBridge implements Provider {
23
+ readonly id: string;
24
+ readonly displayName: string;
25
+ readonly supportsStreaming: boolean;
26
+ readonly prefersJson: boolean;
27
+ readonly capabilities?: string[];
28
+
29
+ private runner: AdapterRunner;
30
+ private def: AdapterDefinition;
31
+
32
+ constructor(definition: AdapterDefinition) {
33
+ this.def = definition;
34
+ this.runner = new AdapterRunner(definition);
35
+
36
+ this.id = definition.id;
37
+ this.displayName = definition.displayName ?? definition.id;
38
+ this.supportsStreaming = (definition.streaming ?? "none") !== "none";
39
+ this.prefersJson = definition.output === "json";
40
+ this.capabilities = definition.capabilities;
41
+ }
42
+
43
+ /**
44
+ * Run the adapter once (non-streaming)
45
+ */
46
+ async runOnce(
47
+ req: CodingRequest,
48
+ opts: ProviderInvokeOptions,
49
+ ): Promise<{
50
+ text: string;
51
+ usage?: { inputTokens?: number; outputTokens?: number; totalTokens?: number };
52
+ }> {
53
+ try {
54
+ const result = await this.runner.run(req.prompt, {
55
+ signal: opts.signal,
56
+ cwd: opts.cwd,
57
+ env: opts.env,
58
+ timeoutMs: opts.timeoutMs,
59
+ });
60
+
61
+ return {
62
+ text: result.text,
63
+ usage: result.usage,
64
+ };
65
+ } catch (err) {
66
+ // Re-throw adapter errors with proper context
67
+ if (err instanceof AdapterError) {
68
+ throw err;
69
+ }
70
+ throw err;
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Run the adapter with streaming
76
+ */
77
+ async *runStream(req: CodingRequest, opts: ProviderInvokeOptions): AsyncGenerator<CodingEvent> {
78
+ const requestId = crypto.randomUUID();
79
+
80
+ yield {
81
+ type: "start",
82
+ provider: this.id,
83
+ requestId,
84
+ };
85
+
86
+ try {
87
+ for await (const chunk of this.runner.runStream(req.prompt, {
88
+ signal: opts.signal,
89
+ cwd: opts.cwd,
90
+ env: opts.env,
91
+ timeoutMs: opts.timeoutMs,
92
+ })) {
93
+ switch (chunk.type) {
94
+ case "start":
95
+ // Already emitted start, skip
96
+ break;
97
+
98
+ case "text":
99
+ yield { type: "text_delta", text: chunk.content };
100
+ break;
101
+
102
+ case "json":
103
+ yield { type: "chunk", data: chunk.data };
104
+ break;
105
+
106
+ case "complete":
107
+ yield {
108
+ type: "complete",
109
+ provider: this.id,
110
+ text: chunk.text,
111
+ usage: chunk.usage,
112
+ };
113
+ break;
114
+
115
+ case "error":
116
+ yield {
117
+ type: "error",
118
+ provider: this.id,
119
+ code: chunk.kind,
120
+ message: chunk.message,
121
+ };
122
+ break;
123
+ }
124
+ }
125
+ } catch (err) {
126
+ const error = err as Error;
127
+ const kind =
128
+ err instanceof AdapterError ? err.kind : this.classifyError({ stderr: error.message });
129
+
130
+ yield {
131
+ type: "error",
132
+ provider: this.id,
133
+ code: kind,
134
+ message: error.message,
135
+ };
136
+ }
137
+ }
138
+
139
+ /**
140
+ * Classify an error
141
+ */
142
+ classifyError(error: ProviderErrorContext): ProviderErrorKind {
143
+ const combined = ((error.stderr ?? "") + (error.stdout ?? "")).toLowerCase();
144
+
145
+ // Check adapter-specific patterns first
146
+ if (this.def.errorPatterns) {
147
+ for (const [kind, patterns] of Object.entries(this.def.errorPatterns)) {
148
+ if (patterns?.some((p) => combined.includes(p.toLowerCase()))) {
149
+ return kind as ProviderErrorKind;
150
+ }
151
+ }
152
+ }
153
+
154
+ // Fall back to generic classification
155
+ if (combined.includes("rate limit") || combined.includes("429")) {
156
+ return "RATE_LIMIT";
157
+ }
158
+ if (combined.includes("unauthorized") || combined.includes("401")) {
159
+ return "UNAUTHORIZED";
160
+ }
161
+ if (combined.includes("quota") || combined.includes("credits")) {
162
+ return "OUT_OF_CREDITS";
163
+ }
164
+ if (combined.includes("timeout")) {
165
+ return "TIMEOUT";
166
+ }
167
+
168
+ return "TRANSIENT";
169
+ }
170
+
171
+ /**
172
+ * Get provider info
173
+ */
174
+ getInfo(): ProviderInfo {
175
+ return {
176
+ id: this.id,
177
+ displayName: this.displayName,
178
+ supportsStreaming: this.supportsStreaming,
179
+ prefersJson: this.prefersJson,
180
+ capabilities: this.capabilities,
181
+ };
182
+ }
183
+ }
184
+
185
+ /**
186
+ * Create a Provider from an AdapterDefinition
187
+ */
188
+ export function createProviderFromAdapter(definition: AdapterDefinition): Provider {
189
+ return new AdapterProviderBridge(definition);
190
+ }