@dtoolkit/dproxy 0.2.0 → 1.0.0
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/README.md +184 -25
- package/dist/index.js +97 -173
- package/package.json +6 -1
package/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<img src="
|
|
2
|
+
<img src="https://raw.githubusercontent.com/ivncmp/dtoolkit/main/logo.png" alt="dtoolkit" />
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
5
|
<h1 align="center">@dtoolkit/dproxy</h1>
|
|
@@ -20,22 +20,162 @@ npm install -g @dtoolkit/dproxy
|
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
22
|
dproxy init # interactive setup wizard
|
|
23
|
-
dproxy "explain this" # single-shot prompt
|
|
23
|
+
dproxy "explain this" # single-shot prompt (default provider)
|
|
24
24
|
dproxy chat # interactive REPL
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
+
## Providers
|
|
28
|
+
|
|
29
|
+
dproxy supports 5 providers out of the box. Each provider shells out to its respective CLI:
|
|
30
|
+
|
|
31
|
+
| Provider | CLI | Features |
|
|
32
|
+
| --- | --- | --- |
|
|
33
|
+
| `claude` (default) | [Claude Code](https://docs.anthropic.com/en/docs/claude-code) | Sessions, cost, usage, tools, system prompt |
|
|
34
|
+
| `codex` | [Codex](https://github.com/openai/codex) | Usage, approval modes |
|
|
35
|
+
| `gemini` | [Gemini CLI](https://github.com/google-gemini/gemini-cli) | Sessions, usage, yolo mode |
|
|
36
|
+
| `ollama` | [Ollama](https://ollama.com/) | Local/offline, any model |
|
|
37
|
+
| `opencode` | [OpenCode](https://github.com/nicholasgriffintn/opencode) | Sessions, cost, usage |
|
|
38
|
+
|
|
39
|
+
### Switching providers
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# Per-command
|
|
43
|
+
dproxy -p ollama "explain this code"
|
|
44
|
+
dproxy ask -p gemini "write a test for this"
|
|
45
|
+
dproxy chat -p codex
|
|
46
|
+
|
|
47
|
+
# Set default in config
|
|
48
|
+
dproxy config set provider.default gemini
|
|
49
|
+
```
|
|
50
|
+
|
|
27
51
|
## Commands
|
|
28
52
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
53
|
+
### `dproxy <prompt>` / `dproxy ask <prompt>`
|
|
54
|
+
|
|
55
|
+
Single-shot prompt with context injection. Reads from stdin if piped.
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
# Basic usage
|
|
59
|
+
dproxy "explain what this function does"
|
|
60
|
+
|
|
61
|
+
# With a specific provider and model
|
|
62
|
+
dproxy -p ollama -m codellama "refactor this"
|
|
63
|
+
|
|
64
|
+
# Pipe content
|
|
65
|
+
cat src/index.ts | dproxy "review this code"
|
|
66
|
+
git diff | dproxy ask "summarize these changes"
|
|
67
|
+
|
|
68
|
+
# Raw JSON output
|
|
69
|
+
dproxy --raw "hello"
|
|
70
|
+
|
|
71
|
+
# JSON output format
|
|
72
|
+
dproxy -o json "hello"
|
|
73
|
+
|
|
74
|
+
# Token usage footer
|
|
75
|
+
dproxy --token-footer "explain monads"
|
|
76
|
+
|
|
77
|
+
# Skip memory/life context
|
|
78
|
+
dproxy --no-memory --no-life "just answer directly"
|
|
79
|
+
|
|
80
|
+
# Inject specific memory keys only
|
|
81
|
+
dproxy --memory "project-rules,style-guide" "review this"
|
|
82
|
+
|
|
83
|
+
# Limit agent turns and budget
|
|
84
|
+
dproxy --max-turns 5 --max-budget-usd 0.50 "refactor the auth module"
|
|
85
|
+
|
|
86
|
+
# System prompt override
|
|
87
|
+
dproxy ask --system-prompt "You are a security auditor" "review this code"
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### `dproxy chat`
|
|
91
|
+
|
|
92
|
+
Interactive REPL with session tracking across turns.
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
# Start a new chat
|
|
96
|
+
dproxy chat
|
|
97
|
+
|
|
98
|
+
# Chat with a specific provider
|
|
99
|
+
dproxy chat -p gemini
|
|
100
|
+
|
|
101
|
+
# Continue the last conversation
|
|
102
|
+
dproxy chat -c
|
|
103
|
+
|
|
104
|
+
# Resume a specific session
|
|
105
|
+
dproxy chat -r sess_abc123
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### `dproxy history`
|
|
109
|
+
|
|
110
|
+
Manage prompt history.
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
dproxy history list # show recent entries
|
|
114
|
+
dproxy history show <id> # show a specific entry
|
|
115
|
+
dproxy history search <q> # search history
|
|
116
|
+
dproxy history clear # clear all history
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### `dproxy memory`
|
|
120
|
+
|
|
121
|
+
Named memory snippets injected into every prompt.
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
dproxy memory list # list all snippets
|
|
125
|
+
dproxy memory get <key> # show a snippet
|
|
126
|
+
dproxy memory set <key> # set (reads from stdin or editor)
|
|
127
|
+
dproxy memory rm <key> # delete a snippet
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### `dproxy template`
|
|
131
|
+
|
|
132
|
+
YAML prompt templates with `{{variable}}` interpolation.
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
dproxy template list # list templates
|
|
136
|
+
dproxy template show <name> # show a template
|
|
137
|
+
dproxy template run <name> # execute a template
|
|
138
|
+
dproxy template create # create a new template
|
|
139
|
+
dproxy template rm <name> # delete a template
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### `dproxy config`
|
|
143
|
+
|
|
144
|
+
Get/set configuration values.
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
dproxy config # show full config
|
|
148
|
+
dproxy config get provider.default # get a value
|
|
149
|
+
dproxy config set provider.default ollama # set a value
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### `dproxy init`
|
|
153
|
+
|
|
154
|
+
Interactive setup wizard. Required before first use.
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
dproxy init
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Flags reference
|
|
161
|
+
|
|
162
|
+
| Flag | Scope | Description |
|
|
163
|
+
| --- | --- | --- |
|
|
164
|
+
| `-p, --provider <name>` | ask, chat | Provider: `claude`, `codex`, `gemini`, `ollama`, `opencode` |
|
|
165
|
+
| `-m, --model <model>` | ask, chat | Model to use |
|
|
166
|
+
| `--max-turns <n>` | ask, chat | Max agent turns per message |
|
|
167
|
+
| `--max-budget-usd <n>` | ask | Max budget in USD |
|
|
168
|
+
| `-o, --output-format <fmt>` | ask | Output format: `text`, `json`, `stream-json` |
|
|
169
|
+
| `--system-prompt <text>` | ask | System prompt override |
|
|
170
|
+
| `--no-memory` | ask, chat | Skip memory injection |
|
|
171
|
+
| `--memory <keys>` | ask | Inject specific memory keys (comma-separated) |
|
|
172
|
+
| `--no-life` | ask, chat | Skip life/PARA context |
|
|
173
|
+
| `--no-history` | ask | Don't save to history |
|
|
174
|
+
| `--raw` | ask | Print raw JSON response |
|
|
175
|
+
| `--token-footer` | ask | Append token usage footer |
|
|
176
|
+
| `--max-session-tokens <n>` | ask | Reset session if context exceeds this |
|
|
177
|
+
| `-c, --continue` | ask, chat | Continue last conversation |
|
|
178
|
+
| `-r, --resume <id>` | ask, chat | Resume a specific session |
|
|
39
179
|
|
|
40
180
|
## Context injection
|
|
41
181
|
|
|
@@ -46,19 +186,38 @@ Every prompt is enriched with context from multiple sources, in priority order:
|
|
|
46
186
|
3. **Memory snippets** — named markdown snippets (truncated to 4,000 chars)
|
|
47
187
|
4. **Life/PARA context** — semantic knowledge base (truncated to 12,000 chars)
|
|
48
188
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
189
|
+
Disable with `--no-memory` and `--no-life`.
|
|
190
|
+
|
|
191
|
+
## Provider configuration
|
|
192
|
+
|
|
193
|
+
Configure provider-specific options in `~/.dproxy/config.json`:
|
|
194
|
+
|
|
195
|
+
```json
|
|
196
|
+
{
|
|
197
|
+
"provider": {
|
|
198
|
+
"default": "claude",
|
|
199
|
+
"claude": {
|
|
200
|
+
"bin": "claude",
|
|
201
|
+
"skipPermissions": false
|
|
202
|
+
},
|
|
203
|
+
"codex": {
|
|
204
|
+
"bin": "codex",
|
|
205
|
+
"approval": "suggest"
|
|
206
|
+
},
|
|
207
|
+
"gemini": {
|
|
208
|
+
"bin": "gemini",
|
|
209
|
+
"yolo": false
|
|
210
|
+
},
|
|
211
|
+
"ollama": {
|
|
212
|
+
"bin": "ollama",
|
|
213
|
+
"defaultModel": "llama3"
|
|
214
|
+
},
|
|
215
|
+
"opencode": {
|
|
216
|
+
"bin": "opencode",
|
|
217
|
+
"skipPermissions": false
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
62
221
|
```
|
|
63
222
|
|
|
64
223
|
## Data storage
|
package/dist/index.js
CHANGED
|
@@ -8,8 +8,32 @@ import pc7 from "picocolors";
|
|
|
8
8
|
import { Command } from "commander";
|
|
9
9
|
import pc from "picocolors";
|
|
10
10
|
|
|
11
|
-
// src/
|
|
12
|
-
import {
|
|
11
|
+
// src/lib/adapter.ts
|
|
12
|
+
import { createClaudeAdapter } from "@dtoolkit/adapter-claude";
|
|
13
|
+
import { createCodexAdapter } from "@dtoolkit/adapter-codex";
|
|
14
|
+
import { createGeminiAdapter } from "@dtoolkit/adapter-gemini";
|
|
15
|
+
import { createOllamaAdapter } from "@dtoolkit/adapter-ollama";
|
|
16
|
+
import { createOpenCodeAdapter } from "@dtoolkit/adapter-opencode";
|
|
17
|
+
function resolveAdapter(provider, config) {
|
|
18
|
+
switch (provider) {
|
|
19
|
+
case "claude":
|
|
20
|
+
return createClaudeAdapter(config.provider.claude);
|
|
21
|
+
case "codex":
|
|
22
|
+
return createCodexAdapter(config.provider.codex);
|
|
23
|
+
case "gemini":
|
|
24
|
+
return createGeminiAdapter(config.provider.gemini);
|
|
25
|
+
case "ollama":
|
|
26
|
+
return createOllamaAdapter(config.provider.ollama);
|
|
27
|
+
case "opencode":
|
|
28
|
+
return createOpenCodeAdapter(config.provider.opencode);
|
|
29
|
+
default:
|
|
30
|
+
throw new Error(`Unknown provider: ${provider}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// src/lib/chat-log-store.ts
|
|
35
|
+
import { appendFile, mkdir as mkdir2, readFile as readFile2 } from "fs/promises";
|
|
36
|
+
import { join as join2 } from "path";
|
|
13
37
|
|
|
14
38
|
// src/lib/config.ts
|
|
15
39
|
import { readFile, writeFile, rename, mkdir } from "fs/promises";
|
|
@@ -51,9 +75,13 @@ var DEFAULT_CONFIG = {
|
|
|
51
75
|
assistantPrefix: "Assistant:",
|
|
52
76
|
sectionHeader: "## Today's conversation"
|
|
53
77
|
},
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
skipPermissions: false
|
|
78
|
+
provider: {
|
|
79
|
+
default: "claude",
|
|
80
|
+
claude: { bin: "claude", skipPermissions: false },
|
|
81
|
+
codex: { bin: "codex", approval: "suggest" },
|
|
82
|
+
gemini: { bin: "gemini", yolo: false },
|
|
83
|
+
ollama: { bin: "ollama", defaultModel: "llama3" },
|
|
84
|
+
opencode: { bin: "opencode", skipPermissions: false }
|
|
57
85
|
},
|
|
58
86
|
defaults: {},
|
|
59
87
|
debug: false
|
|
@@ -84,6 +112,10 @@ async function loadConfig() {
|
|
|
84
112
|
try {
|
|
85
113
|
const raw = await readFile(join(DATA_DIR, "config.json"), "utf-8");
|
|
86
114
|
const parsed = JSON.parse(raw);
|
|
115
|
+
if (parsed.claude && !parsed.provider) {
|
|
116
|
+
parsed.provider = { default: "claude", claude: parsed.claude };
|
|
117
|
+
delete parsed.claude;
|
|
118
|
+
}
|
|
87
119
|
return deepMerge(
|
|
88
120
|
DEFAULT_CONFIG,
|
|
89
121
|
parsed
|
|
@@ -132,135 +164,7 @@ async function setConfigValue(key, value) {
|
|
|
132
164
|
await saveConfig(config);
|
|
133
165
|
}
|
|
134
166
|
|
|
135
|
-
// src/claude.ts
|
|
136
|
-
async function execClaude(options) {
|
|
137
|
-
const config = await loadConfig();
|
|
138
|
-
const args = buildArgs(options, config.claude?.skipPermissions ?? false);
|
|
139
|
-
const bin = config.claude?.bin || "claude";
|
|
140
|
-
const startTime = Date.now();
|
|
141
|
-
return new Promise((resolve, reject) => {
|
|
142
|
-
const proc = spawn(bin, args, {
|
|
143
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
144
|
-
env: { ...process.env }
|
|
145
|
-
});
|
|
146
|
-
let stdout = "";
|
|
147
|
-
let stderr = "";
|
|
148
|
-
proc.stdout.on("data", (data) => {
|
|
149
|
-
stdout += data.toString();
|
|
150
|
-
});
|
|
151
|
-
proc.stderr.on("data", (data) => {
|
|
152
|
-
stderr += data.toString();
|
|
153
|
-
});
|
|
154
|
-
if (options.stdinContent) {
|
|
155
|
-
proc.stdin.write(options.stdinContent);
|
|
156
|
-
proc.stdin.end();
|
|
157
|
-
} else {
|
|
158
|
-
proc.stdin.end();
|
|
159
|
-
}
|
|
160
|
-
proc.on("error", (err) => {
|
|
161
|
-
if (err.code === "ENOENT") {
|
|
162
|
-
reject(
|
|
163
|
-
new Error("claude CLI not found. Make sure Claude Code is installed and on your PATH.")
|
|
164
|
-
);
|
|
165
|
-
} else {
|
|
166
|
-
reject(err);
|
|
167
|
-
}
|
|
168
|
-
});
|
|
169
|
-
proc.on("close", (code) => {
|
|
170
|
-
const durationMs = Date.now() - startTime;
|
|
171
|
-
if (code !== 0 && !stdout.trim()) {
|
|
172
|
-
reject(new Error(stderr || `claude exited with code ${code}`));
|
|
173
|
-
return;
|
|
174
|
-
}
|
|
175
|
-
try {
|
|
176
|
-
const parsed = JSON.parse(stdout);
|
|
177
|
-
const u = parsed.usage ?? {};
|
|
178
|
-
const usage = {
|
|
179
|
-
input: u.input_tokens ?? 0,
|
|
180
|
-
output: u.output_tokens ?? 0,
|
|
181
|
-
cacheWrite: u.cache_creation_input_tokens ?? 0,
|
|
182
|
-
cacheRead: u.cache_read_input_tokens ?? 0,
|
|
183
|
-
total: (u.input_tokens ?? 0) + (u.output_tokens ?? 0) + (u.cache_creation_input_tokens ?? 0) + (u.cache_read_input_tokens ?? 0)
|
|
184
|
-
};
|
|
185
|
-
resolve({
|
|
186
|
-
result: parsed.result ?? parsed.content ?? stdout,
|
|
187
|
-
sessionId: parsed.session_id ?? "",
|
|
188
|
-
costUsd: parsed.cost_usd ?? parsed.total_cost_usd ?? 0,
|
|
189
|
-
durationMs,
|
|
190
|
-
isError: parsed.is_error ?? false,
|
|
191
|
-
usage,
|
|
192
|
-
raw: parsed
|
|
193
|
-
});
|
|
194
|
-
} catch {
|
|
195
|
-
resolve({
|
|
196
|
-
result: stdout.trim(),
|
|
197
|
-
sessionId: "",
|
|
198
|
-
costUsd: 0,
|
|
199
|
-
durationMs,
|
|
200
|
-
isError: code !== 0
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
});
|
|
204
|
-
});
|
|
205
|
-
}
|
|
206
|
-
function buildArgs(options, skipPermissions) {
|
|
207
|
-
const args = ["--print", "--output-format", "json"];
|
|
208
|
-
if (skipPermissions) {
|
|
209
|
-
args.push("--dangerously-skip-permissions");
|
|
210
|
-
}
|
|
211
|
-
if (options.model) {
|
|
212
|
-
args.push("--model", options.model);
|
|
213
|
-
}
|
|
214
|
-
if (options.maxTurns !== void 0) {
|
|
215
|
-
args.push("--max-turns", String(options.maxTurns));
|
|
216
|
-
}
|
|
217
|
-
if (options.maxBudgetUsd !== void 0) {
|
|
218
|
-
args.push("--max-budget-usd", String(options.maxBudgetUsd));
|
|
219
|
-
}
|
|
220
|
-
if (options.systemPrompt) {
|
|
221
|
-
args.push("--system-prompt", options.systemPrompt);
|
|
222
|
-
}
|
|
223
|
-
if (options.appendSystemPrompt) {
|
|
224
|
-
args.push("--append-system-prompt", options.appendSystemPrompt);
|
|
225
|
-
}
|
|
226
|
-
if (options.resumeSessionId) {
|
|
227
|
-
args.push("--resume", options.resumeSessionId);
|
|
228
|
-
}
|
|
229
|
-
if (options.continueSession) {
|
|
230
|
-
args.push("--continue");
|
|
231
|
-
}
|
|
232
|
-
if (options.allowedTools) {
|
|
233
|
-
for (const tool of options.allowedTools) {
|
|
234
|
-
args.push("--allowedTools", tool);
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
if (options.additionalArgs) {
|
|
238
|
-
args.push(...options.additionalArgs);
|
|
239
|
-
}
|
|
240
|
-
args.push(options.prompt);
|
|
241
|
-
return args;
|
|
242
|
-
}
|
|
243
|
-
async function readStdin() {
|
|
244
|
-
if (process.stdin.isTTY) {
|
|
245
|
-
return "";
|
|
246
|
-
}
|
|
247
|
-
return new Promise((resolve) => {
|
|
248
|
-
let data = "";
|
|
249
|
-
const timeout = setTimeout(() => resolve(data), 5e3);
|
|
250
|
-
process.stdin.setEncoding("utf-8");
|
|
251
|
-
process.stdin.on("data", (chunk) => {
|
|
252
|
-
data += chunk;
|
|
253
|
-
});
|
|
254
|
-
process.stdin.on("end", () => {
|
|
255
|
-
clearTimeout(timeout);
|
|
256
|
-
resolve(data);
|
|
257
|
-
});
|
|
258
|
-
});
|
|
259
|
-
}
|
|
260
|
-
|
|
261
167
|
// src/lib/chat-log-store.ts
|
|
262
|
-
import { appendFile, mkdir as mkdir2, readFile as readFile2 } from "fs/promises";
|
|
263
|
-
import { join as join2 } from "path";
|
|
264
168
|
function getTodayFile(dir) {
|
|
265
169
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
266
170
|
return join2(dir, `${today}.md`);
|
|
@@ -744,9 +648,28 @@ async function updateSessionTokens(sessionId, totalTokens) {
|
|
|
744
648
|
await saveState(pruned);
|
|
745
649
|
}
|
|
746
650
|
|
|
651
|
+
// src/lib/stdin.ts
|
|
652
|
+
async function readStdin() {
|
|
653
|
+
if (process.stdin.isTTY) {
|
|
654
|
+
return "";
|
|
655
|
+
}
|
|
656
|
+
return new Promise((resolve) => {
|
|
657
|
+
let data = "";
|
|
658
|
+
const timeout = setTimeout(() => resolve(data), 5e3);
|
|
659
|
+
process.stdin.setEncoding("utf-8");
|
|
660
|
+
process.stdin.on("data", (chunk) => {
|
|
661
|
+
data += chunk;
|
|
662
|
+
});
|
|
663
|
+
process.stdin.on("end", () => {
|
|
664
|
+
clearTimeout(timeout);
|
|
665
|
+
resolve(data);
|
|
666
|
+
});
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
|
|
747
670
|
// src/commands/ask.ts
|
|
748
671
|
function createAskCommand() {
|
|
749
|
-
return new Command("ask").description("Send a prompt to
|
|
672
|
+
return new Command("ask").description("Send a prompt to an AI model").argument("[prompt...]", "The prompt to send").option("-p, --provider <provider>", "Provider to use (claude, codex, gemini, ollama, opencode)").option("-m, --model <model>", "Model to use").option("--max-turns <n>", "Max agent turns", parseInt).option("--max-budget-usd <n>", "Max budget in USD", parseFloat).option("-o, --output-format <format>", "Output format: text, json, stream-json").option("--system-prompt <text>", "System prompt override").option("--no-memory", "Skip memory injection").option("--memory <keys>", "Inject only specific memory keys (comma-separated)").option("--no-life", "Skip life/PARA context injection").option("--no-history", "Don't save to history").option("--raw", "Print raw JSON response").option("--token-footer", "Append token usage footer to response text").option(
|
|
750
673
|
"--max-session-tokens <n>",
|
|
751
674
|
"Reset session if context exceeds this token count",
|
|
752
675
|
parseInt
|
|
@@ -786,70 +709,68 @@ ${stdinContent}` : stdinContent;
|
|
|
786
709
|
},
|
|
787
710
|
config
|
|
788
711
|
) || void 0;
|
|
789
|
-
let
|
|
712
|
+
let sessionId = opts.resume;
|
|
790
713
|
const maxSessionTokens = opts.maxSessionTokens;
|
|
791
|
-
if (
|
|
792
|
-
const currentTokens = await getSessionTokens(
|
|
714
|
+
if (sessionId && maxSessionTokens) {
|
|
715
|
+
const currentTokens = await getSessionTokens(sessionId);
|
|
793
716
|
if (currentTokens > maxSessionTokens) {
|
|
794
|
-
|
|
717
|
+
sessionId = void 0;
|
|
795
718
|
}
|
|
796
719
|
}
|
|
797
|
-
const
|
|
720
|
+
const providerName = opts.provider ?? config.provider.default;
|
|
721
|
+
const adapter = resolveAdapter(providerName, config);
|
|
722
|
+
const request = {
|
|
798
723
|
prompt: fullPrompt,
|
|
799
724
|
model: opts.model ?? config.defaults.model,
|
|
800
725
|
maxTurns: opts.maxTurns ?? config.defaults.maxTurns,
|
|
801
|
-
maxBudgetUsd: opts.maxBudgetUsd,
|
|
802
726
|
systemPrompt: opts.systemPrompt,
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
727
|
+
sessionId,
|
|
728
|
+
continueSession: opts.continue,
|
|
729
|
+
options: {
|
|
730
|
+
appendSystemPrompt,
|
|
731
|
+
maxBudgetUsd: opts.maxBudgetUsd
|
|
732
|
+
}
|
|
806
733
|
};
|
|
807
|
-
const result = await
|
|
808
|
-
const resultText = result.result;
|
|
734
|
+
const result = await adapter.execute(request);
|
|
809
735
|
if (result.sessionId && result.usage) {
|
|
810
|
-
await updateSessionTokens(result.sessionId, result.usage.
|
|
736
|
+
await updateSessionTokens(result.sessionId, result.usage.totalTokens);
|
|
811
737
|
}
|
|
812
738
|
if (opts.tokenFooter && result.usage) {
|
|
813
739
|
const u = result.usage;
|
|
814
740
|
const parts = [];
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
parts.push(
|
|
818
|
-
parts.push(`out:${u.output.toLocaleString()}`);
|
|
819
|
-
parts.push(`~${u.total.toLocaleString()}`);
|
|
741
|
+
parts.push(`in:${u.inputTokens.toLocaleString()}`);
|
|
742
|
+
parts.push(`out:${u.outputTokens.toLocaleString()}`);
|
|
743
|
+
parts.push(`~${u.totalTokens.toLocaleString()}`);
|
|
820
744
|
const footer = `
|
|
821
745
|
|
|
822
746
|
\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014
|
|
823
747
|
\`${parts.join(" \xB7 ")}\``;
|
|
824
|
-
result.
|
|
825
|
-
if (result.raw && typeof result.raw === "object") {
|
|
826
|
-
result.raw.result = result.result;
|
|
827
|
-
}
|
|
748
|
+
result.text += footer;
|
|
828
749
|
}
|
|
829
750
|
if (opts.raw) {
|
|
830
751
|
console.log(JSON.stringify(result.raw ?? result, null, 2));
|
|
831
752
|
} else if (opts.outputFormat === "json") {
|
|
832
753
|
console.log(
|
|
833
754
|
JSON.stringify(
|
|
834
|
-
{ result: result.
|
|
755
|
+
{ result: result.text, sessionId: result.sessionId, costUsd: result.costUsd },
|
|
835
756
|
null,
|
|
836
757
|
2
|
|
837
758
|
)
|
|
838
759
|
);
|
|
839
760
|
} else {
|
|
840
|
-
console.log(result.
|
|
761
|
+
console.log(result.text);
|
|
841
762
|
}
|
|
842
763
|
if (opts.history !== false) {
|
|
843
764
|
await addHistoryEntry({
|
|
844
765
|
prompt: fullPrompt,
|
|
845
|
-
result: result.
|
|
846
|
-
sessionId: result.sessionId,
|
|
847
|
-
costUsd: result.costUsd,
|
|
766
|
+
result: result.text,
|
|
767
|
+
sessionId: result.sessionId ?? "",
|
|
768
|
+
costUsd: result.costUsd ?? 0,
|
|
848
769
|
durationMs: result.durationMs,
|
|
849
770
|
model: opts.model ?? config.defaults.model
|
|
850
771
|
});
|
|
851
772
|
}
|
|
852
|
-
void addChatLog(promptText,
|
|
773
|
+
void addChatLog(promptText, result.text);
|
|
853
774
|
}
|
|
854
775
|
|
|
855
776
|
// src/commands/chat.ts
|
|
@@ -876,7 +797,7 @@ async function saveSession(session) {
|
|
|
876
797
|
);
|
|
877
798
|
}
|
|
878
799
|
function createChatCommand() {
|
|
879
|
-
return new Command2("chat").description("Start an interactive conversation
|
|
800
|
+
return new Command2("chat").description("Start an interactive conversation").option("-p, --provider <provider>", "Provider to use (claude, codex, gemini, ollama, opencode)").option("-c, --continue", "Continue last conversation").option("-r, --resume <id>", "Resume a specific session").option("-m, --model <model>", "Model to use").option("--max-turns <n>", "Max agent turns per message", parseInt).option("--no-memory", "Skip memory injection").option("--no-life", "Skip life/PARA context injection").action(async (opts) => {
|
|
880
801
|
try {
|
|
881
802
|
await runChat(opts);
|
|
882
803
|
} catch (err) {
|
|
@@ -887,6 +808,8 @@ function createChatCommand() {
|
|
|
887
808
|
}
|
|
888
809
|
async function runChat(opts) {
|
|
889
810
|
const config = await loadConfig();
|
|
811
|
+
const providerName = opts.provider ?? config.provider.default;
|
|
812
|
+
const adapter = resolveAdapter(providerName, config);
|
|
890
813
|
let sessionId;
|
|
891
814
|
if (opts.resume) {
|
|
892
815
|
sessionId = opts.resume;
|
|
@@ -909,7 +832,7 @@ async function runChat(opts) {
|
|
|
909
832
|
},
|
|
910
833
|
config
|
|
911
834
|
) || void 0;
|
|
912
|
-
console.log(pc2.bold(pc2.blue(
|
|
835
|
+
console.log(pc2.bold(pc2.blue(`${adapter.provider} Chat`)));
|
|
913
836
|
console.log(pc2.dim('Type "exit" or Ctrl+C to quit.\n'));
|
|
914
837
|
const rl = createInterface({
|
|
915
838
|
input: process.stdin,
|
|
@@ -931,15 +854,15 @@ async function runChat(opts) {
|
|
|
931
854
|
rl.pause();
|
|
932
855
|
try {
|
|
933
856
|
process.stdout.write(pc2.dim("thinking...\r"));
|
|
934
|
-
const result = await
|
|
857
|
+
const result = await adapter.execute({
|
|
935
858
|
prompt: input,
|
|
936
859
|
model: opts.model ?? config.defaults.model,
|
|
937
860
|
maxTurns: opts.maxTurns ?? config.defaults.maxTurns,
|
|
938
|
-
|
|
939
|
-
|
|
861
|
+
sessionId,
|
|
862
|
+
options: { appendSystemPrompt }
|
|
940
863
|
});
|
|
941
864
|
process.stdout.write("\r" + " ".repeat(20) + "\r");
|
|
942
|
-
console.log(pc2.cyan(
|
|
865
|
+
console.log(pc2.cyan(`${adapter.provider} > `) + result.text);
|
|
943
866
|
console.log();
|
|
944
867
|
if (!sessionId && result.sessionId) {
|
|
945
868
|
sessionId = result.sessionId;
|
|
@@ -950,13 +873,13 @@ async function runChat(opts) {
|
|
|
950
873
|
}
|
|
951
874
|
await addHistoryEntry({
|
|
952
875
|
prompt: input,
|
|
953
|
-
result: result.
|
|
954
|
-
sessionId: result.sessionId,
|
|
955
|
-
costUsd: result.costUsd,
|
|
876
|
+
result: result.text,
|
|
877
|
+
sessionId: result.sessionId ?? "",
|
|
878
|
+
costUsd: result.costUsd ?? 0,
|
|
956
879
|
durationMs: result.durationMs,
|
|
957
880
|
model: opts.model ?? config.defaults.model
|
|
958
881
|
});
|
|
959
|
-
void addChatLog(input, result.
|
|
882
|
+
void addChatLog(input, result.text);
|
|
960
883
|
} catch (err) {
|
|
961
884
|
console.error(pc2.red(err.message));
|
|
962
885
|
}
|
|
@@ -1351,7 +1274,8 @@ function createTemplateCommand() {
|
|
|
1351
1274
|
|
|
1352
1275
|
// src/index.ts
|
|
1353
1276
|
var program = new Command7();
|
|
1354
|
-
program.
|
|
1277
|
+
program.enablePositionalOptions();
|
|
1278
|
+
program.name("dproxy").description("Universal adapter for invoking models via local CLIs").version("1.0.0");
|
|
1355
1279
|
program.addCommand(createInitCommand());
|
|
1356
1280
|
var guarded = (cmd) => {
|
|
1357
1281
|
cmd.hook("preAction", async () => {
|
|
@@ -1382,7 +1306,7 @@ configCmd.action(async () => {
|
|
|
1382
1306
|
console.log(JSON.stringify(config, null, 2));
|
|
1383
1307
|
});
|
|
1384
1308
|
program.addCommand(guarded(configCmd));
|
|
1385
|
-
program.argument("[prompt...]", "Send a quick prompt (shorthand for 'dproxy ask')").option("-m, --model <model>", "Model to use").option("--max-turns <n>", "Max agent turns", parseInt).option("--max-budget-usd <n>", "Max budget in USD", parseFloat).option("-o, --output-format <format>", "Output format").option("--no-memory", "Skip memory injection").option("--memory <keys>", "Inject specific memory keys").option("--no-life", "Skip life/PARA context injection").option("--no-history", "Don't save to history").option("--raw", "Print raw JSON response").option("--token-footer", "Append token usage footer to response text").option("--max-session-tokens <n>", "Reset session if context exceeds this token count", parseInt).option("-c, --continue", "Continue last conversation").option("-r, --resume <id>", "Resume a specific session").action(async (promptParts, opts) => {
|
|
1309
|
+
program.argument("[prompt...]", "Send a quick prompt (shorthand for 'dproxy ask')").option("-p, --provider <provider>", "Provider to use (claude, codex, gemini, ollama, opencode)").option("-m, --model <model>", "Model to use").option("--max-turns <n>", "Max agent turns", parseInt).option("--max-budget-usd <n>", "Max budget in USD", parseFloat).option("-o, --output-format <format>", "Output format").option("--no-memory", "Skip memory injection").option("--memory <keys>", "Inject specific memory keys").option("--no-life", "Skip life/PARA context injection").option("--no-history", "Don't save to history").option("--raw", "Print raw JSON response").option("--token-footer", "Append token usage footer to response text").option("--max-session-tokens <n>", "Reset session if context exceeds this token count", parseInt).option("-c, --continue", "Continue last conversation").option("-r, --resume <id>", "Resume a specific session").action(async (promptParts, opts) => {
|
|
1386
1310
|
if (promptParts.length === 0) {
|
|
1387
1311
|
program.help();
|
|
1388
1312
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dtoolkit/dproxy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Universal adapter for invoking models via local CLIs",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -35,6 +35,11 @@
|
|
|
35
35
|
"picocolors": "^1.1.1",
|
|
36
36
|
"commander": "^13.0.0",
|
|
37
37
|
"yaml": "^2.7.0",
|
|
38
|
+
"@dtoolkit/adapter-claude": "1.0.0",
|
|
39
|
+
"@dtoolkit/adapter-codex": "1.0.0",
|
|
40
|
+
"@dtoolkit/adapter-gemini": "1.0.0",
|
|
41
|
+
"@dtoolkit/adapter-ollama": "1.0.0",
|
|
42
|
+
"@dtoolkit/adapter-opencode": "1.0.0",
|
|
38
43
|
"@dtoolkit/core": "0.1.0"
|
|
39
44
|
},
|
|
40
45
|
"devDependencies": {
|