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,258 @@
1
+ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
2
+ import { OpenCodeProvider } from "../../../providers/opencode";
3
+ import type { CodingRequest, ProviderConfig } from "../../../types";
4
+
5
+ // Mock Bun.spawn for provider tests
6
+ const createMockProcess = (stdout: string, stderr = "", exitCode = 0) => {
7
+ const encoder = new TextEncoder();
8
+ const stdoutData = encoder.encode(stdout);
9
+ const stderrData = encoder.encode(stderr);
10
+
11
+ return {
12
+ stdin: {
13
+ write: mock(() => {}),
14
+ end: mock(() => {}),
15
+ },
16
+ stdout: {
17
+ getReader: () => {
18
+ let read = false;
19
+ return {
20
+ read: async () => {
21
+ if (!read) {
22
+ read = true;
23
+ return { done: false, value: stdoutData };
24
+ }
25
+ return { done: true, value: undefined };
26
+ },
27
+ releaseLock: () => {},
28
+ };
29
+ },
30
+ },
31
+ stderr: {
32
+ getReader: () => {
33
+ let read = false;
34
+ return {
35
+ read: async () => {
36
+ if (!read && stderr) {
37
+ read = true;
38
+ return { done: false, value: stderrData };
39
+ }
40
+ return { done: true, value: undefined };
41
+ },
42
+ releaseLock: () => {},
43
+ };
44
+ },
45
+ },
46
+ exited: Promise.resolve(exitCode),
47
+ kill: mock(() => {}),
48
+ };
49
+ };
50
+
51
+ let mockSpawn: ReturnType<typeof mock>;
52
+
53
+ describe("OpenCodeProvider", () => {
54
+ let provider: OpenCodeProvider;
55
+ let config: ProviderConfig;
56
+ let originalSpawn: typeof Bun.spawn;
57
+
58
+ beforeEach(() => {
59
+ // Save original and mock Bun.spawn
60
+ originalSpawn = Bun.spawn;
61
+ mockSpawn = mock(() => createMockProcess('{"summary": "Task completed", "response": "Done"}'));
62
+ (globalThis as any).Bun.spawn = mockSpawn;
63
+ });
64
+
65
+ afterEach(() => {
66
+ // Restore original Bun.spawn
67
+ (globalThis as any).Bun.spawn = originalSpawn;
68
+ });
69
+
70
+ describe("constructor", () => {
71
+ test("should initialize with correct id and display name", () => {
72
+ config = {
73
+ binary: "opencode",
74
+ args: [],
75
+ jsonMode: "flag",
76
+ jsonFlag: "-f",
77
+ streamingMode: "none",
78
+ capabilities: ["generate", "edit", "explain", "test", "refactor"],
79
+ };
80
+ provider = new OpenCodeProvider(config);
81
+
82
+ expect(provider.id).toBe("opencode");
83
+ expect(provider.displayName).toBe("OpenCode Agent");
84
+ });
85
+
86
+ test("should default to 'opencode' binary if not specified", () => {
87
+ config = {
88
+ binary: "",
89
+ args: [],
90
+ jsonMode: "flag",
91
+ streamingMode: "none",
92
+ capabilities: [],
93
+ };
94
+ provider = new OpenCodeProvider(config);
95
+
96
+ expect(provider.id).toBe("opencode");
97
+ });
98
+ });
99
+
100
+ describe("runOnce", () => {
101
+ beforeEach(() => {
102
+ config = {
103
+ binary: "opencode",
104
+ args: [],
105
+ jsonMode: "flag",
106
+ jsonFlag: "-f",
107
+ streamingMode: "none",
108
+ capabilities: ["generate", "edit", "explain", "test", "refactor"],
109
+ };
110
+ provider = new OpenCodeProvider(config);
111
+ });
112
+
113
+ test("should execute process successfully with JSON output", async () => {
114
+ const req: CodingRequest = {
115
+ prompt: "Fix the bug in utils.ts",
116
+ mode: "edit",
117
+ stream: false,
118
+ };
119
+
120
+ const result = await provider.runOnce(req, {});
121
+
122
+ expect(result.text).toBe("Task completed");
123
+ expect(mockSpawn).toHaveBeenCalled();
124
+ });
125
+
126
+ test("should include files in prompt when provided", async () => {
127
+ const req: CodingRequest = {
128
+ prompt: "Refactor this code",
129
+ mode: "refactor",
130
+ files: ["src/utils.ts", "src/main.ts"],
131
+ stream: false,
132
+ };
133
+
134
+ await provider.runOnce(req, {});
135
+
136
+ // Verify the spawn call includes files in the prompt
137
+ expect(mockSpawn).toHaveBeenCalled();
138
+ const spawnCall = mockSpawn.mock.calls[0];
139
+ const args = spawnCall[0] as string[];
140
+ // The args should include 'agent', 'run', the prompt (which includes files), '-f', 'json', '-q'
141
+ expect(args).toContain("agent");
142
+ expect(args).toContain("run");
143
+ expect(args).toContain("-f");
144
+ expect(args).toContain("json");
145
+ expect(args).toContain("-q");
146
+ });
147
+
148
+ test("should parse plain text output when JSON fails", async () => {
149
+ mockSpawn = mock(() => createMockProcess("Fixed the authentication bug in login.ts"));
150
+ (globalThis as any).Bun.spawn = mockSpawn;
151
+
152
+ const req: CodingRequest = {
153
+ prompt: "Fix auth bug",
154
+ mode: "edit",
155
+ stream: false,
156
+ };
157
+
158
+ const result = await provider.runOnce(req, {});
159
+
160
+ expect(result.text).toBe("Fixed the authentication bug in login.ts");
161
+ });
162
+ });
163
+
164
+ describe("classifyError", () => {
165
+ beforeEach(() => {
166
+ config = {
167
+ binary: "opencode",
168
+ args: [],
169
+ jsonMode: "flag",
170
+ jsonFlag: "-f",
171
+ streamingMode: "none",
172
+ capabilities: ["generate", "edit", "explain", "test", "refactor"],
173
+ };
174
+ provider = new OpenCodeProvider(config);
175
+ });
176
+
177
+ test("classifies 'rate limit' as RATE_LIMIT", () => {
178
+ const error = provider.classifyError({ stderr: "Rate limit exceeded" });
179
+ expect(error).toBe("RATE_LIMIT");
180
+ });
181
+
182
+ test("classifies 'too many requests' as RATE_LIMIT", () => {
183
+ const error = provider.classifyError({ stderr: "Too many requests" });
184
+ expect(error).toBe("RATE_LIMIT");
185
+ });
186
+
187
+ test("classifies '429' as RATE_LIMIT", () => {
188
+ const error = provider.classifyError({ stderr: "Error 429: Request limit" });
189
+ expect(error).toBe("RATE_LIMIT");
190
+ });
191
+
192
+ test("classifies 'authentication failed' as UNAUTHORIZED", () => {
193
+ const error = provider.classifyError({ stderr: "Authentication failed" });
194
+ expect(error).toBe("UNAUTHORIZED");
195
+ });
196
+
197
+ test("classifies 'api key invalid' as UNAUTHORIZED", () => {
198
+ const error = provider.classifyError({ stderr: "API key invalid" });
199
+ expect(error).toBe("UNAUTHORIZED");
200
+ });
201
+
202
+ test("classifies 'command not found' as NOT_FOUND", () => {
203
+ const error = provider.classifyError({ stderr: "command not found: opencode" });
204
+ expect(error).toBe("NOT_FOUND");
205
+ });
206
+
207
+ test("classifies exit code 127 as NOT_FOUND", () => {
208
+ const error = provider.classifyError({ exitCode: 127, stderr: "" });
209
+ expect(error).toBe("NOT_FOUND");
210
+ });
211
+
212
+ test("classifies 'connection refused' as TRANSIENT", () => {
213
+ const error = provider.classifyError({ stderr: "ECONNREFUSED" });
214
+ expect(error).toBe("TRANSIENT");
215
+ });
216
+
217
+ test("classifies unknown errors as TRANSIENT", () => {
218
+ const error = provider.classifyError({ stderr: "Something unexpected happened" });
219
+ expect(error).toBe("TRANSIENT");
220
+ });
221
+ });
222
+
223
+ describe("extractFilesChanged", () => {
224
+ beforeEach(() => {
225
+ config = {
226
+ binary: "opencode",
227
+ args: [],
228
+ jsonMode: "flag",
229
+ jsonFlag: "-f",
230
+ streamingMode: "none",
231
+ capabilities: ["generate", "edit", "explain", "test", "refactor"],
232
+ };
233
+ provider = new OpenCodeProvider(config);
234
+ });
235
+
236
+ test("should extract files from modified pattern", () => {
237
+ const stdout = "Modified: src/utils.ts\nModified: src/main.ts";
238
+ const files = provider.extractFilesChanged(stdout);
239
+
240
+ expect(files).toContain("src/utils.ts");
241
+ expect(files).toContain("src/main.ts");
242
+ });
243
+
244
+ test("should extract files from created pattern", () => {
245
+ const stdout = "Created: src/new-file.ts";
246
+ const files = provider.extractFilesChanged(stdout);
247
+
248
+ expect(files).toContain("src/new-file.ts");
249
+ });
250
+
251
+ test("should deduplicate files", () => {
252
+ const stdout = "Modified: src/utils.ts\nUpdated: src/utils.ts";
253
+ const files = provider.extractFilesChanged(stdout);
254
+
255
+ expect(files.filter((f) => f === "src/utils.ts").length).toBe(1);
256
+ });
257
+ });
258
+ });
@@ -0,0 +1,195 @@
1
+ import { afterEach, beforeEach, describe, expect, mock, spyOn, test } from "bun:test";
2
+ import { QwenCodeProvider } from "../../../providers/qwen-code";
3
+ import type { CodingRequest, ProviderConfig } from "../../../types";
4
+
5
+ // Mock Bun.spawn for provider tests
6
+ const createMockProcess = (stdout: string, stderr = "", exitCode = 0) => {
7
+ const encoder = new TextEncoder();
8
+ const stdoutData = encoder.encode(stdout);
9
+ const stderrData = encoder.encode(stderr);
10
+
11
+ return {
12
+ stdin: {
13
+ write: mock(() => {}),
14
+ end: mock(() => {}),
15
+ },
16
+ stdout: {
17
+ getReader: () => {
18
+ let read = false;
19
+ return {
20
+ read: async () => {
21
+ if (!read) {
22
+ read = true;
23
+ return { done: false, value: stdoutData };
24
+ }
25
+ return { done: true, value: undefined };
26
+ },
27
+ releaseLock: () => {},
28
+ };
29
+ },
30
+ },
31
+ stderr: {
32
+ getReader: () => {
33
+ let read = false;
34
+ return {
35
+ read: async () => {
36
+ if (!read && stderr) {
37
+ read = true;
38
+ return { done: false, value: stderrData };
39
+ }
40
+ return { done: true, value: undefined };
41
+ },
42
+ releaseLock: () => {},
43
+ };
44
+ },
45
+ },
46
+ exited: Promise.resolve(exitCode),
47
+ kill: mock(() => {}),
48
+ };
49
+ };
50
+
51
+ let mockSpawn: ReturnType<typeof mock>;
52
+
53
+ describe("QwenCodeProvider", () => {
54
+ let provider: QwenCodeProvider;
55
+ let config: ProviderConfig;
56
+ let originalSpawn: typeof Bun.spawn;
57
+
58
+ beforeEach(() => {
59
+ // Save original and mock Bun.spawn
60
+ originalSpawn = Bun.spawn;
61
+ mockSpawn = mock(() => createMockProcess("test output"));
62
+ (globalThis as any).Bun.spawn = mockSpawn;
63
+ });
64
+
65
+ afterEach(() => {
66
+ // Restore original Bun.spawn
67
+ (globalThis as any).Bun.spawn = originalSpawn;
68
+ });
69
+
70
+ describe("constructor", () => {
71
+ test("should initialize with correct id and display name", () => {
72
+ config = {
73
+ binary: "qwen",
74
+ args: [],
75
+ jsonMode: "flag",
76
+ jsonFlag: "--json",
77
+ streamingMode: "jsonl",
78
+ capabilities: ["generate", "edit", "explain", "test"],
79
+ };
80
+ provider = new QwenCodeProvider(config);
81
+
82
+ expect(provider.id).toBe("qwen-code");
83
+ expect(provider.displayName).toBe("Qwen Code CLI");
84
+ });
85
+ });
86
+
87
+ describe("runOnce", () => {
88
+ beforeEach(() => {
89
+ config = {
90
+ binary: "qwen",
91
+ args: [],
92
+ jsonMode: "none",
93
+ streamingMode: "line",
94
+ capabilities: ["generate", "edit", "explain", "test"],
95
+ };
96
+ provider = new QwenCodeProvider(config);
97
+ });
98
+
99
+ test("should execute process successfully", async () => {
100
+ const req: CodingRequest = {
101
+ prompt: "Write hello world",
102
+ mode: "generate",
103
+ stream: false,
104
+ };
105
+
106
+ const result = await provider.runOnce(req, {});
107
+
108
+ expect(result.text).toBe("test output");
109
+ expect(mockSpawn).toHaveBeenCalled();
110
+ });
111
+ });
112
+
113
+ describe("runStream", () => {
114
+ beforeEach(() => {
115
+ config = {
116
+ binary: "qwen",
117
+ args: [],
118
+ jsonMode: "none",
119
+ streamingMode: "line",
120
+ capabilities: ["generate", "edit", "explain", "test"],
121
+ };
122
+ provider = new QwenCodeProvider(config);
123
+ });
124
+
125
+ test("should stream process output successfully", async () => {
126
+ const req: CodingRequest = {
127
+ prompt: "Stream test",
128
+ mode: "generate",
129
+ stream: true,
130
+ };
131
+
132
+ const events = [];
133
+ for await (const event of provider.runStream(req, {})) {
134
+ events.push(event);
135
+ }
136
+
137
+ // Should have start, text_delta, and complete events
138
+ expect(events.length).toBeGreaterThanOrEqual(3);
139
+ expect(events[0]).toEqual({
140
+ type: "start",
141
+ provider: "qwen-code",
142
+ requestId: expect.any(String),
143
+ });
144
+ // Find text_delta event
145
+ const deltaEvent = events.find((e: any) => e.type === "text_delta");
146
+ expect(deltaEvent).toBeDefined();
147
+ expect(deltaEvent.text).toBe("test output");
148
+ // Last event should be complete
149
+ expect(events[events.length - 1].type).toBe("complete");
150
+ });
151
+ });
152
+
153
+ describe("classifyError", () => {
154
+ beforeEach(() => {
155
+ config = {
156
+ binary: "qwen",
157
+ args: [],
158
+ jsonMode: "none",
159
+ streamingMode: "line",
160
+ capabilities: ["generate", "edit", "explain", "test"],
161
+ };
162
+ provider = new QwenCodeProvider(config);
163
+ });
164
+
165
+ test("classifies 'quota exceeded' as OUT_OF_CREDITS", () => {
166
+ const error = provider.classifyError({ stderr: "Quota exceeded" });
167
+ expect(error).toBe("OUT_OF_CREDITS");
168
+ });
169
+
170
+ test("classifies 'rate limit' as RATE_LIMIT", () => {
171
+ const error = provider.classifyError({ stderr: "Rate limit exceeded" });
172
+ expect(error).toBe("RATE_LIMIT");
173
+ });
174
+
175
+ test("classifies '401 unauthorized' as UNAUTHORIZED", () => {
176
+ const error = provider.classifyError({ stderr: "401 unauthorized" });
177
+ expect(error).toBe("UNAUTHORIZED");
178
+ });
179
+
180
+ test("classifies '500 internal error' as INTERNAL", () => {
181
+ const error = provider.classifyError({ stderr: "500 internal server error" });
182
+ expect(error).toBe("INTERNAL");
183
+ });
184
+
185
+ test("classifies 'timeout' as TIMEOUT", () => {
186
+ const error = provider.classifyError({ stderr: "Request timeout" });
187
+ expect(error).toBe("TIMEOUT");
188
+ });
189
+
190
+ test("classifies unknown errors as TRANSIENT", () => {
191
+ const error = provider.classifyError({ stderr: "Something unexpected happened" });
192
+ expect(error).toBe("TRANSIENT");
193
+ });
194
+ });
195
+ });
@@ -0,0 +1,18 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { CodexProvider } from "../../../providers/codex";
3
+
4
+ describe("Simple Codex Test", () => {
5
+ test("can create CodexProvider", () => {
6
+ const config = {
7
+ binary: "cdx",
8
+ args: [],
9
+ jsonMode: "none" as const,
10
+ streamingMode: "line" as const,
11
+ capabilities: ["generate", "edit", "test"],
12
+ };
13
+
14
+ const provider = new CodexProvider(config);
15
+ expect(provider.id).toBe("codex");
16
+ expect(provider.displayName).toBe("Codex CLI");
17
+ });
18
+ });