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.
- package/bin/wraptc +4 -4
- package/package.json +2 -2
- package/src/cli/__tests__/cli.test.ts +337 -0
- package/src/cli/index.ts +149 -0
- package/src/core/__tests__/fixtures/configs/project-config.json +14 -0
- package/src/core/__tests__/fixtures/configs/system-config.json +14 -0
- package/src/core/__tests__/fixtures/configs/user-config.json +15 -0
- package/src/core/__tests__/integration/integration.test.ts +241 -0
- package/src/core/__tests__/integration/mock-coder-adapter.test.ts +243 -0
- package/src/core/__tests__/test-utils.ts +136 -0
- package/src/core/__tests__/unit/adapters/runner.test.ts +302 -0
- package/src/core/__tests__/unit/basic-test.test.ts +44 -0
- package/src/core/__tests__/unit/basic.test.ts +12 -0
- package/src/core/__tests__/unit/config.test.ts +244 -0
- package/src/core/__tests__/unit/error-patterns.test.ts +181 -0
- package/src/core/__tests__/unit/memory-monitor.test.ts +354 -0
- package/src/core/__tests__/unit/plugin/registry.test.ts +356 -0
- package/src/core/__tests__/unit/providers/codex.test.ts +173 -0
- package/src/core/__tests__/unit/providers/configurable.test.ts +429 -0
- package/src/core/__tests__/unit/providers/gemini.test.ts +251 -0
- package/src/core/__tests__/unit/providers/opencode.test.ts +258 -0
- package/src/core/__tests__/unit/providers/qwen-code.test.ts +195 -0
- package/src/core/__tests__/unit/providers/simple-codex.test.ts +18 -0
- package/src/core/__tests__/unit/router.test.ts +967 -0
- package/src/core/__tests__/unit/state.test.ts +1079 -0
- package/src/core/__tests__/unit/unified/capabilities.test.ts +186 -0
- package/src/core/__tests__/unit/wrap-terminalcoder.test.ts +32 -0
- package/src/core/adapters/builtin/codex.ts +35 -0
- package/src/core/adapters/builtin/gemini.ts +34 -0
- package/src/core/adapters/builtin/index.ts +31 -0
- package/src/core/adapters/builtin/mock-coder.ts +148 -0
- package/src/core/adapters/builtin/qwen.ts +34 -0
- package/src/core/adapters/define.ts +48 -0
- package/src/core/adapters/index.ts +43 -0
- package/src/core/adapters/loader.ts +143 -0
- package/src/core/adapters/provider-bridge.ts +190 -0
- package/src/core/adapters/runner.ts +437 -0
- package/src/core/adapters/types.ts +172 -0
- package/src/core/config.ts +290 -0
- package/src/core/define-provider.ts +212 -0
- package/src/core/error-patterns.ts +147 -0
- package/src/core/index.ts +130 -0
- package/src/core/memory-monitor.ts +171 -0
- package/src/core/plugin/builtin.ts +87 -0
- package/src/core/plugin/index.ts +34 -0
- package/src/core/plugin/registry.ts +350 -0
- package/src/core/plugin/types.ts +209 -0
- package/src/core/provider-factory.ts +397 -0
- package/src/core/provider-loader.ts +171 -0
- package/src/core/providers/codex.ts +56 -0
- package/src/core/providers/configurable.ts +637 -0
- package/src/core/providers/custom.ts +261 -0
- package/src/core/providers/gemini.ts +41 -0
- package/src/core/providers/index.ts +383 -0
- package/src/core/providers/opencode.ts +168 -0
- package/src/core/providers/qwen-code.ts +41 -0
- package/src/core/router.ts +370 -0
- package/src/core/state.ts +258 -0
- package/src/core/types.ts +206 -0
- package/src/core/unified/capabilities.ts +184 -0
- package/src/core/unified/errors.ts +141 -0
- package/src/core/unified/index.ts +29 -0
- package/src/core/unified/output.ts +189 -0
- package/src/core/wrap-terminalcoder.ts +245 -0
- package/src/mcp/__tests__/server.test.ts +295 -0
- package/src/mcp/server.ts +284 -0
- package/src/test-fixtures/mock-coder.sh +194 -0
- package/dist/cli/index.js +0 -16501
- package/dist/core/index.js +0 -7531
- package/dist/mcp/server.js +0 -14568
- 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
|
+
}
|