@vellumai/assistant 0.10.7-dev.202607100313.fffdfea → 0.10.7-dev.202607100720.fba456e
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/openapi.yaml +846 -27
- package/package.json +1 -1
- package/src/__tests__/config-watcher-skill-reseed.test.ts +1 -0
- package/src/__tests__/config-watcher.test.ts +22 -4
- package/src/__tests__/credential-security-invariants.test.ts +1 -1
- package/src/__tests__/plugin-import-boundary-guard.test.ts +6 -9
- package/src/__tests__/plugin-import-boundary-reverse-guard.test.ts +0 -2
- package/src/cli/commands/__tests__/inference-models.test.ts +86 -0
- package/src/cli/commands/__tests__/inference-profiles.test.ts +254 -0
- package/src/cli/commands/__tests__/inference-providers.test.ts +171 -0
- package/src/cli/commands/__tests__/schedules.test.ts +133 -1
- package/src/cli/commands/__tests__/usage.test.ts +131 -0
- package/src/cli/commands/inference-callsites.ts +158 -0
- package/src/cli/commands/inference-models.ts +84 -0
- package/src/cli/commands/inference-profiles.ts +371 -0
- package/src/cli/commands/inference-providers-default.ts +89 -0
- package/src/cli/commands/inference-providers.ts +87 -16
- package/src/cli/commands/inference.ts +6 -0
- package/src/cli/commands/memory/nodes.ts +90 -50
- package/src/cli/commands/schedules.ts +17 -2
- package/src/cli/commands/usage.ts +59 -21
- package/src/cli/lib/__tests__/cli-output.test.ts +20 -0
- package/src/cli/lib/cli-output.ts +55 -0
- package/src/config/__tests__/loader-last-known-good.test.ts +229 -0
- package/src/config/__tests__/loader-sparse-array-cleanup.test.ts +166 -0
- package/src/config/default-profile-catalog.ts +28 -0
- package/src/config/loader.ts +276 -46
- package/src/daemon/config-watcher.ts +65 -20
- package/src/daemon/message-types/sync.ts +1 -0
- package/src/oauth/byo-connection.test.ts +1 -1
- package/src/oauth/connection-resolver.test.ts +140 -0
- package/src/oauth/connection-resolver.ts +48 -12
- package/src/plugins/defaults/memory/anisotropy.ts +1 -1
- package/src/plugins/defaults/memory/config.ts +1 -1
- package/src/plugins/defaults/memory/context-search/agent-protocol.ts +1 -1
- package/src/plugins/defaults/memory/context-search/search.ts +1 -1
- package/src/plugins/defaults/memory/graph/bootstrap.ts +1 -1
- package/src/plugins/defaults/memory/graph/consolidation.ts +1 -1
- package/src/plugins/defaults/memory/graph/conversation-graph-memory.ts +11 -1
- package/src/plugins/defaults/memory/graph/extraction.ts +1 -1
- package/src/plugins/defaults/memory/graph/narrative.ts +1 -1
- package/src/plugins/defaults/memory/graph/pattern-scan.ts +1 -1
- package/src/plugins/defaults/memory/graph/tool-handlers.ts +1 -1
- package/src/plugins/defaults/memory/graph/tools.ts +1 -1
- package/src/plugins/defaults/memory/graph-topology/build-memory-graph.ts +43 -16
- package/src/plugins/defaults/memory/hooks/user-prompt-submit.ts +1 -1
- package/src/plugins/defaults/memory/host-utils.ts +80 -0
- package/src/plugins/defaults/memory/identity-context.ts +1 -1
- package/src/plugins/defaults/memory/injectors.ts +1 -1
- package/src/plugins/defaults/memory/jobs/embed-concept-page.ts +2 -2
- package/src/plugins/defaults/memory/jobs-worker.ts +2 -9
- package/src/plugins/defaults/memory/memory-retrospective-state.ts +1 -1
- package/src/plugins/defaults/memory/paths.ts +48 -0
- package/src/plugins/defaults/memory/pkb/autoinject.ts +1 -1
- package/src/plugins/defaults/memory/pkb/context.ts +2 -2
- package/src/plugins/defaults/memory/pkb/types.ts +1 -1
- package/src/plugins/defaults/memory/pkb-schedule.ts +2 -2
- package/src/plugins/defaults/memory/rerank-local.ts +2 -3
- package/src/plugins/defaults/memory/routes/memory-eval-routes.ts +1 -1
- package/src/plugins/defaults/memory/routes/memory-item-routes.test.ts +149 -1
- package/src/plugins/defaults/memory/routes/memory-item-routes.ts +144 -1
- package/src/plugins/defaults/memory/routes/memory-v2-routes.ts +1 -1
- package/src/plugins/defaults/memory/startup.ts +1 -1
- package/src/plugins/defaults/memory/v2/backfill-jobs.ts +1 -1
- package/src/plugins/defaults/memory/v2/consolidation-job.ts +2 -2
- package/src/plugins/defaults/memory/v2/injection.ts +1 -1
- package/src/plugins/defaults/memory/v2/memory-v2-startup.ts +1 -1
- package/src/plugins/defaults/memory/v2/prompts/consolidation.ts +1 -1
- package/src/plugins/defaults/memory/v2/qdrant.ts +1 -1
- package/src/plugins/defaults/memory/v2/reranker.ts +1 -1
- package/src/plugins/defaults/memory/v2/static-context.ts +1 -1
- package/src/plugins/defaults/memory/v2/sweep-job.ts +1 -1
- package/src/plugins/defaults/memory/v3/candidate-match.ts +1 -1
- package/src/plugins/defaults/memory/v3/maintain-job.ts +1 -1
- package/src/plugins/defaults/memory/v3/page-content.ts +1 -1
- package/src/plugins/defaults/memory/v3/pool-select.ts +1 -2
- package/src/plugins/defaults/memory/v3/selection-log-store.ts +1 -1
- package/src/plugins/defaults/memory/v3/shadow-plugin.ts +2 -5
- package/src/plugins/defaults/memory/worker.ts +1 -1
- package/src/plugins/defaults/workspace/injectors.ts +17 -15
- package/src/plugins/types.ts +9 -14
- package/src/providers/inference/connection-availability.ts +193 -0
- package/src/runtime/routes/__tests__/inference-callsites-routes.test.ts +54 -0
- package/src/runtime/routes/__tests__/inference-models-routes.test.ts +54 -0
- package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +405 -0
- package/src/runtime/routes/__tests__/inference-routes-registration.test.ts +42 -0
- package/src/runtime/routes/__tests__/theme-routes.test.ts +66 -0
- package/src/runtime/routes/conversation-query-routes.ts +5 -3
- package/src/runtime/routes/default-provider-routes.ts +5 -152
- package/src/runtime/routes/index.ts +8 -0
- package/src/runtime/routes/inference-callsites-routes.ts +211 -0
- package/src/runtime/routes/inference-models-routes.ts +104 -0
- package/src/runtime/routes/inference-profiles-routes.ts +717 -0
- package/src/runtime/routes/theme-routes.ts +36 -0
- package/src/runtime/sync/resource-sync-events.ts +4 -0
- package/src/theme/workspace-theme.test.ts +269 -0
- package/src/theme/workspace-theme.ts +304 -0
- package/src/watcher/__tests__/engine-auth-notify.test.ts +316 -0
- package/src/watcher/engine.ts +87 -2
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `assistant inference profiles` CLI namespace.
|
|
3
|
+
*
|
|
4
|
+
* profiles list — effective profile catalog (managed + user)
|
|
5
|
+
* profiles get <name> — a single effective profile
|
|
6
|
+
* profiles create <name> ... — create a validated custom profile
|
|
7
|
+
* profiles update <name> ... — partial update of a custom profile
|
|
8
|
+
* profiles delete <name> — delete a custom profile (managed protected)
|
|
9
|
+
* profiles active [name] — read or set the active (chat) profile
|
|
10
|
+
*
|
|
11
|
+
* All subcommands delegate to the daemon via IPC. Provider/model/connection
|
|
12
|
+
* validation is enforced by the daemon (`inference_profiles_*` routes); the
|
|
13
|
+
* CLI only shape-parses flags.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { Command } from "commander";
|
|
17
|
+
|
|
18
|
+
import { cliIpcCall } from "../../ipc/cli-client.js";
|
|
19
|
+
import { renderTable, writeCliError, writeLine } from "../lib/cli-output.js";
|
|
20
|
+
|
|
21
|
+
interface ProfileSummary {
|
|
22
|
+
name: string;
|
|
23
|
+
label: string | null;
|
|
24
|
+
provider: string | null;
|
|
25
|
+
model: string | null;
|
|
26
|
+
status: "active" | "disabled";
|
|
27
|
+
source: "managed" | "user";
|
|
28
|
+
provider_connection?: string;
|
|
29
|
+
availability: { status: string; message?: string } | null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface ProfileWriteResult {
|
|
33
|
+
ok: true;
|
|
34
|
+
name: string;
|
|
35
|
+
entry: Record<string, unknown>;
|
|
36
|
+
warnings: string[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
type WriteFlags = {
|
|
40
|
+
provider?: string;
|
|
41
|
+
model?: string;
|
|
42
|
+
connection?: string;
|
|
43
|
+
label?: string;
|
|
44
|
+
effort?: string;
|
|
45
|
+
maxTokens?: string;
|
|
46
|
+
temperature?: string;
|
|
47
|
+
thinking?: string;
|
|
48
|
+
description?: string;
|
|
49
|
+
allowUnlisted?: boolean;
|
|
50
|
+
json?: boolean;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Parse the shared write flags into an IPC body. Returns an error string when
|
|
55
|
+
* a numeric/enum flag is malformed. Only keys the user supplied are included.
|
|
56
|
+
*/
|
|
57
|
+
function buildWriteBody(
|
|
58
|
+
opts: WriteFlags,
|
|
59
|
+
): { ok: true; body: Record<string, unknown> } | { ok: false; error: string } {
|
|
60
|
+
const body: Record<string, unknown> = {};
|
|
61
|
+
if (opts.provider !== undefined) {
|
|
62
|
+
body.provider = opts.provider;
|
|
63
|
+
}
|
|
64
|
+
if (opts.model !== undefined) {
|
|
65
|
+
body.model = opts.model;
|
|
66
|
+
}
|
|
67
|
+
if (opts.connection !== undefined) {
|
|
68
|
+
body.connection = opts.connection;
|
|
69
|
+
}
|
|
70
|
+
if (opts.label !== undefined) {
|
|
71
|
+
body.label = opts.label;
|
|
72
|
+
}
|
|
73
|
+
if (opts.effort !== undefined) {
|
|
74
|
+
body.effort = opts.effort;
|
|
75
|
+
}
|
|
76
|
+
if (opts.description !== undefined) {
|
|
77
|
+
body.description = opts.description;
|
|
78
|
+
}
|
|
79
|
+
if (opts.allowUnlisted) {
|
|
80
|
+
body.allowUnlisted = true;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (opts.maxTokens !== undefined) {
|
|
84
|
+
if (!/^\d+$/.test(opts.maxTokens.trim())) {
|
|
85
|
+
return { ok: false, error: "--max-tokens must be a positive integer." };
|
|
86
|
+
}
|
|
87
|
+
body.maxTokens = Number(opts.maxTokens.trim());
|
|
88
|
+
}
|
|
89
|
+
if (opts.temperature !== undefined) {
|
|
90
|
+
const value = Number(opts.temperature.trim());
|
|
91
|
+
if (!Number.isFinite(value)) {
|
|
92
|
+
return { ok: false, error: "--temperature must be a number." };
|
|
93
|
+
}
|
|
94
|
+
body.temperature = value;
|
|
95
|
+
}
|
|
96
|
+
if (opts.thinking !== undefined) {
|
|
97
|
+
const normalized = opts.thinking.trim().toLowerCase();
|
|
98
|
+
if (normalized !== "on" && normalized !== "off") {
|
|
99
|
+
return { ok: false, error: "--thinking must be 'on' or 'off'." };
|
|
100
|
+
}
|
|
101
|
+
body.thinking = normalized === "on";
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return { ok: true, body };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function printWriteResult(
|
|
108
|
+
verb: string,
|
|
109
|
+
result: ProfileWriteResult,
|
|
110
|
+
json?: boolean,
|
|
111
|
+
): void {
|
|
112
|
+
if (json) {
|
|
113
|
+
process.stdout.write(JSON.stringify(result) + "\n");
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
for (const warning of result.warnings) {
|
|
117
|
+
writeLine(`warning: ${warning}`);
|
|
118
|
+
}
|
|
119
|
+
writeLine(`profile ${result.name} ${verb}`);
|
|
120
|
+
writeLine(
|
|
121
|
+
`Verify it works: assistant inference send --profile ${result.name} "Reply with OK"`,
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function addWriteFlags(cmd: Command): Command {
|
|
126
|
+
return cmd
|
|
127
|
+
.option("--provider <p>", "LLM provider (e.g. anthropic, openai)")
|
|
128
|
+
.option("--model <id>", "Model id (see 'assistant inference models list')")
|
|
129
|
+
.option("--connection <name>", "Provider connection name to use")
|
|
130
|
+
.option("--label <text>", "Human-readable label")
|
|
131
|
+
.option(
|
|
132
|
+
"--effort <tier>",
|
|
133
|
+
"Reasoning effort (none|low|medium|high|xhigh|max)",
|
|
134
|
+
)
|
|
135
|
+
.option("--max-tokens <n>", "Max response tokens")
|
|
136
|
+
.option("--temperature <x>", "Sampling temperature")
|
|
137
|
+
.option("--thinking <on|off>", "Enable or disable thinking")
|
|
138
|
+
.option("--description <text>", "Profile description")
|
|
139
|
+
.option("--allow-unlisted", "Allow a model not in the catalog (warns)")
|
|
140
|
+
.option("--json", "Output as machine-readable JSON");
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function attachProfilesSubcommand(inference: Command): void {
|
|
144
|
+
const profiles = inference
|
|
145
|
+
.command("profiles")
|
|
146
|
+
.description("Manage inference profiles (named model configurations)");
|
|
147
|
+
|
|
148
|
+
profiles.addHelpText(
|
|
149
|
+
"after",
|
|
150
|
+
`
|
|
151
|
+
Profiles are named model configurations. Managed defaults (balanced,
|
|
152
|
+
quality-optimized, cost-optimized) are read-only; create your own to
|
|
153
|
+
customize provider, model, and tuning.
|
|
154
|
+
|
|
155
|
+
Examples:
|
|
156
|
+
$ assistant inference profiles list
|
|
157
|
+
$ assistant inference profiles create my-fast --provider anthropic \\
|
|
158
|
+
--model claude-haiku-4-5 --connection anthropic-personal --effort low
|
|
159
|
+
$ assistant inference profiles update my-fast --effort high
|
|
160
|
+
$ assistant inference profiles active my-fast
|
|
161
|
+
$ assistant inference profiles delete my-fast`,
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
// ── list ────────────────────────────────────────────────────────────
|
|
165
|
+
profiles
|
|
166
|
+
.command("list")
|
|
167
|
+
.description("List the effective profile catalog")
|
|
168
|
+
.option("--json", "Output as machine-readable JSON")
|
|
169
|
+
.action(async (opts: { json?: boolean }) => {
|
|
170
|
+
const ipcResult = await cliIpcCall<{ profiles: ProfileSummary[] }>(
|
|
171
|
+
"inference_profiles_list",
|
|
172
|
+
{},
|
|
173
|
+
);
|
|
174
|
+
if (!ipcResult.ok) {
|
|
175
|
+
writeCliError(ipcResult.error ?? "Unknown error", opts.json);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
const rows = ipcResult.result!.profiles;
|
|
179
|
+
if (opts.json) {
|
|
180
|
+
process.stdout.write(
|
|
181
|
+
JSON.stringify({ ok: true, profiles: rows }) + "\n",
|
|
182
|
+
);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
if (rows.length === 0) {
|
|
186
|
+
writeLine("No profiles found.");
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
renderTable(
|
|
190
|
+
["NAME", "LABEL", "PROVIDER", "MODEL", "STATUS", "SOURCE", "AVAIL"],
|
|
191
|
+
rows.map((p) => [
|
|
192
|
+
p.name,
|
|
193
|
+
p.label ?? "-",
|
|
194
|
+
p.provider ?? "-",
|
|
195
|
+
p.model ?? "-",
|
|
196
|
+
p.status,
|
|
197
|
+
p.source,
|
|
198
|
+
p.availability ? p.availability.status : "-",
|
|
199
|
+
]),
|
|
200
|
+
);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// ── get ─────────────────────────────────────────────────────────────
|
|
204
|
+
profiles
|
|
205
|
+
.command("get <name>")
|
|
206
|
+
.description("Show a single effective profile")
|
|
207
|
+
.option("--json", "Output as machine-readable JSON")
|
|
208
|
+
.action(async (name: string, opts: { json?: boolean }) => {
|
|
209
|
+
const ipcResult = await cliIpcCall<{
|
|
210
|
+
name: string;
|
|
211
|
+
entry: Record<string, unknown>;
|
|
212
|
+
availability: { status: string; message?: string } | null;
|
|
213
|
+
}>("inference_profiles_get", { pathParams: { name } });
|
|
214
|
+
if (!ipcResult.ok) {
|
|
215
|
+
writeCliError(ipcResult.error ?? "Unknown error", opts.json);
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
const result = ipcResult.result!;
|
|
219
|
+
if (opts.json) {
|
|
220
|
+
process.stdout.write(JSON.stringify({ ok: true, ...result }) + "\n");
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
writeLine(`profile: ${result.name}`);
|
|
224
|
+
for (const [key, value] of Object.entries(result.entry)) {
|
|
225
|
+
writeLine(` ${key}: ${JSON.stringify(value)}`);
|
|
226
|
+
}
|
|
227
|
+
if (result.availability) {
|
|
228
|
+
writeLine(` availability: ${result.availability.status}`);
|
|
229
|
+
if (result.availability.message) {
|
|
230
|
+
writeLine(` ${result.availability.message}`);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
// ── create ──────────────────────────────────────────────────────────
|
|
236
|
+
addWriteFlags(
|
|
237
|
+
profiles
|
|
238
|
+
.command("create <name>")
|
|
239
|
+
.description("Create a validated custom profile"),
|
|
240
|
+
).action(async (name: string, opts: WriteFlags) => {
|
|
241
|
+
if (!opts.provider) {
|
|
242
|
+
writeCliError("--provider is required.", opts.json);
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
if (!opts.model) {
|
|
246
|
+
writeCliError("--model is required.", opts.json);
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
const built = buildWriteBody(opts);
|
|
250
|
+
if (!built.ok) {
|
|
251
|
+
writeCliError(built.error, opts.json);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
const ipcResult = await cliIpcCall<ProfileWriteResult>(
|
|
255
|
+
"inference_profiles_create",
|
|
256
|
+
{ body: { ...built.body, name } },
|
|
257
|
+
);
|
|
258
|
+
if (!ipcResult.ok) {
|
|
259
|
+
writeCliError(ipcResult.error ?? "Unknown error", opts.json);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
printWriteResult("created", ipcResult.result!, opts.json);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
// ── update ──────────────────────────────────────────────────────────
|
|
266
|
+
addWriteFlags(
|
|
267
|
+
profiles
|
|
268
|
+
.command("update <name>")
|
|
269
|
+
.description("Partially update a custom profile"),
|
|
270
|
+
).action(async (name: string, opts: WriteFlags) => {
|
|
271
|
+
const built = buildWriteBody(opts);
|
|
272
|
+
if (!built.ok) {
|
|
273
|
+
writeCliError(built.error, opts.json);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
if (Object.keys(built.body).length === 0) {
|
|
277
|
+
writeCliError(
|
|
278
|
+
"Nothing to update — pass at least one field flag.",
|
|
279
|
+
opts.json,
|
|
280
|
+
);
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
const ipcResult = await cliIpcCall<ProfileWriteResult>(
|
|
284
|
+
"inference_profiles_update",
|
|
285
|
+
{ pathParams: { name }, body: built.body },
|
|
286
|
+
);
|
|
287
|
+
if (!ipcResult.ok) {
|
|
288
|
+
writeCliError(ipcResult.error ?? "Unknown error", opts.json);
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
printWriteResult("updated", ipcResult.result!, opts.json);
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
// ── delete ──────────────────────────────────────────────────────────
|
|
295
|
+
profiles
|
|
296
|
+
.command("delete <name>")
|
|
297
|
+
.description("Delete a custom profile")
|
|
298
|
+
.option("--json", "Output as machine-readable JSON")
|
|
299
|
+
.action(async (name: string, opts: { json?: boolean }) => {
|
|
300
|
+
const ipcResult = await cliIpcCall<{ ok: true; name: string }>(
|
|
301
|
+
"inference_profiles_delete",
|
|
302
|
+
{ pathParams: { name } },
|
|
303
|
+
);
|
|
304
|
+
if (!ipcResult.ok) {
|
|
305
|
+
writeCliError(ipcResult.error ?? "Unknown error", opts.json);
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
if (opts.json) {
|
|
309
|
+
process.stdout.write(
|
|
310
|
+
JSON.stringify({ ok: true, name: ipcResult.result!.name }) + "\n",
|
|
311
|
+
);
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
writeLine(`profile ${name} deleted`);
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
// ── active ──────────────────────────────────────────────────────────
|
|
318
|
+
profiles
|
|
319
|
+
.command("active [name]")
|
|
320
|
+
.description("Read or set the active (chat) profile")
|
|
321
|
+
.option("--json", "Output as machine-readable JSON")
|
|
322
|
+
.addHelpText(
|
|
323
|
+
"after",
|
|
324
|
+
`
|
|
325
|
+
With no argument, prints the active profile. With a name, sets it — the
|
|
326
|
+
same deep-merge write the model picker performs.
|
|
327
|
+
|
|
328
|
+
Examples:
|
|
329
|
+
$ assistant inference profiles active
|
|
330
|
+
$ assistant inference profiles active balanced`,
|
|
331
|
+
)
|
|
332
|
+
.action(async (name: string | undefined, opts: { json?: boolean }) => {
|
|
333
|
+
if (name === undefined) {
|
|
334
|
+
const ipcResult = await cliIpcCall<{
|
|
335
|
+
llm?: { activeProfile?: string };
|
|
336
|
+
}>("config_get");
|
|
337
|
+
if (!ipcResult.ok) {
|
|
338
|
+
writeCliError(ipcResult.error ?? "Unknown error", opts.json);
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
const active = ipcResult.result!.llm?.activeProfile ?? null;
|
|
342
|
+
if (opts.json) {
|
|
343
|
+
process.stdout.write(
|
|
344
|
+
JSON.stringify({ ok: true, activeProfile: active }) + "\n",
|
|
345
|
+
);
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
writeLine(
|
|
349
|
+
active ? `active profile: ${active}` : "no active profile set",
|
|
350
|
+
);
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const ipcResult = await cliIpcCall<{ ok: true; activeProfile: string }>(
|
|
355
|
+
"inference_profiles_set_active",
|
|
356
|
+
{ body: { name } },
|
|
357
|
+
);
|
|
358
|
+
if (!ipcResult.ok) {
|
|
359
|
+
writeCliError(ipcResult.error ?? "Unknown error", opts.json);
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
const activeProfile = ipcResult.result!.activeProfile;
|
|
363
|
+
if (opts.json) {
|
|
364
|
+
process.stdout.write(
|
|
365
|
+
JSON.stringify({ ok: true, activeProfile }) + "\n",
|
|
366
|
+
);
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
writeLine(`active profile set to ${activeProfile}`);
|
|
370
|
+
});
|
|
371
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `assistant inference providers default [name]` — read or set the workspace
|
|
3
|
+
* default provider (`llm.defaultProvider`), wrapping the daemon's
|
|
4
|
+
* `llm_default_provider_get` / `llm_default_provider_put` routes. The read
|
|
5
|
+
* form prints the resolved connection and its availability status.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Command } from "commander";
|
|
9
|
+
|
|
10
|
+
import { cliIpcCall } from "../../ipc/cli-client.js";
|
|
11
|
+
import { writeCliError, writeLine } from "../lib/cli-output.js";
|
|
12
|
+
|
|
13
|
+
interface DefaultProviderStatus {
|
|
14
|
+
provider: string | null;
|
|
15
|
+
connectionName?: string;
|
|
16
|
+
resolvedConnectionName: string | null;
|
|
17
|
+
availability: { status: string; message?: string };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function printStatus(status: DefaultProviderStatus, json?: boolean): void {
|
|
21
|
+
if (json) {
|
|
22
|
+
process.stdout.write(JSON.stringify({ ok: true, ...status }) + "\n");
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
if (!status.provider) {
|
|
26
|
+
writeLine("no default provider configured");
|
|
27
|
+
} else {
|
|
28
|
+
writeLine(`default provider: ${status.provider}`);
|
|
29
|
+
if (status.resolvedConnectionName) {
|
|
30
|
+
writeLine(` connection: ${status.resolvedConnectionName}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
writeLine(` availability: ${status.availability.status}`);
|
|
34
|
+
if (status.availability.message) {
|
|
35
|
+
writeLine(` ${status.availability.message}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function attachDefaultProviderSubcommand(providers: Command): void {
|
|
40
|
+
providers
|
|
41
|
+
.command("default [name]")
|
|
42
|
+
.description("Read or set the default provider (prints availability)")
|
|
43
|
+
.option("--connection <name>", "Pin a specific connection when setting")
|
|
44
|
+
.option("--json", "Output as machine-readable JSON")
|
|
45
|
+
.addHelpText(
|
|
46
|
+
"after",
|
|
47
|
+
`
|
|
48
|
+
With no argument, prints the default provider and whether it is usable.
|
|
49
|
+
With a provider name, sets it (optionally pinning a connection).
|
|
50
|
+
|
|
51
|
+
Examples:
|
|
52
|
+
$ assistant inference providers default
|
|
53
|
+
$ assistant inference providers default anthropic
|
|
54
|
+
$ assistant inference providers default anthropic --connection anthropic-personal`,
|
|
55
|
+
)
|
|
56
|
+
.action(
|
|
57
|
+
async (
|
|
58
|
+
name: string | undefined,
|
|
59
|
+
opts: { connection?: string; json?: boolean },
|
|
60
|
+
) => {
|
|
61
|
+
if (name === undefined) {
|
|
62
|
+
const ipcResult = await cliIpcCall<DefaultProviderStatus>(
|
|
63
|
+
"llm_default_provider_get",
|
|
64
|
+
{},
|
|
65
|
+
);
|
|
66
|
+
if (!ipcResult.ok) {
|
|
67
|
+
writeCliError(ipcResult.error ?? "Unknown error", opts.json);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
printStatus(ipcResult.result!, opts.json);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const body: Record<string, unknown> = { provider: name };
|
|
75
|
+
if (opts.connection !== undefined) {
|
|
76
|
+
body.connectionName = opts.connection;
|
|
77
|
+
}
|
|
78
|
+
const ipcResult = await cliIpcCall<DefaultProviderStatus>(
|
|
79
|
+
"llm_default_provider_put",
|
|
80
|
+
{ body },
|
|
81
|
+
);
|
|
82
|
+
if (!ipcResult.ok) {
|
|
83
|
+
writeCliError(ipcResult.error ?? "Unknown error", opts.json);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
printStatus(ipcResult.result!, opts.json);
|
|
87
|
+
},
|
|
88
|
+
);
|
|
89
|
+
}
|
|
@@ -18,7 +18,8 @@ import type { Command } from "commander";
|
|
|
18
18
|
|
|
19
19
|
import { cliIpcCall } from "../../ipc/cli-client.js";
|
|
20
20
|
import type { OAuth2Config } from "../../security/oauth2.js";
|
|
21
|
-
import {
|
|
21
|
+
import { writeCliError } from "../lib/cli-output.js";
|
|
22
|
+
import { attachDefaultProviderSubcommand } from "./inference-providers-default.js";
|
|
22
23
|
|
|
23
24
|
// ---------------------------------------------------------------------------
|
|
24
25
|
// Response types
|
|
@@ -159,32 +160,55 @@ function buildAuthInput(
|
|
|
159
160
|
credential?: string,
|
|
160
161
|
): Record<string, unknown> | string {
|
|
161
162
|
if (authType === "api_key") {
|
|
162
|
-
if (!credential)
|
|
163
|
+
if (!credential) {
|
|
164
|
+
return "--credential is required when --auth api_key";
|
|
165
|
+
}
|
|
163
166
|
return { type: "api_key", credential };
|
|
164
167
|
}
|
|
165
168
|
if (authType === "platform") {
|
|
166
|
-
if (credential)
|
|
169
|
+
if (credential) {
|
|
170
|
+
return "--credential is not accepted with --auth platform";
|
|
171
|
+
}
|
|
167
172
|
return { type: "platform" };
|
|
168
173
|
}
|
|
169
174
|
if (authType === "none") {
|
|
170
|
-
if (credential)
|
|
175
|
+
if (credential) {
|
|
176
|
+
return "--credential is not accepted with --auth none";
|
|
177
|
+
}
|
|
171
178
|
return { type: "none" };
|
|
172
179
|
}
|
|
173
180
|
if (authType === "oauth_subscription") {
|
|
174
|
-
if (!credential)
|
|
181
|
+
if (!credential) {
|
|
175
182
|
return "--credential is required when --auth oauth_subscription";
|
|
183
|
+
}
|
|
176
184
|
return { type: "oauth_subscription", credential };
|
|
177
185
|
}
|
|
178
186
|
return `Unknown auth type "${authType}". Use: api_key, platform, none, oauth_subscription`;
|
|
179
187
|
}
|
|
180
188
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
189
|
+
/** Commander collector for a repeatable option (e.g. `--model` multiple times). */
|
|
190
|
+
function collectRepeatable(value: string, previous: string[]): string[] {
|
|
191
|
+
return [...previous, value];
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Build the openai-compatible custom-provider fields (`base_url`, `models`)
|
|
196
|
+
* from CLI flags, forwarded to the connection route under its exact field
|
|
197
|
+
* names. The daemon stays authoritative on whether they are required/allowed;
|
|
198
|
+
* the CLI only shape-forwards what the user passed.
|
|
199
|
+
*/
|
|
200
|
+
function buildCustomProviderFields(opts: {
|
|
201
|
+
baseUrl?: string;
|
|
202
|
+
model?: string[];
|
|
203
|
+
}): Record<string, unknown> {
|
|
204
|
+
const fields: Record<string, unknown> = {};
|
|
205
|
+
if (opts.baseUrl !== undefined) {
|
|
206
|
+
fields.base_url = opts.baseUrl;
|
|
207
|
+
}
|
|
208
|
+
if (opts.model !== undefined && opts.model.length > 0) {
|
|
209
|
+
fields.models = opts.model.map((id) => ({ id }));
|
|
186
210
|
}
|
|
187
|
-
|
|
211
|
+
return fields;
|
|
188
212
|
}
|
|
189
213
|
|
|
190
214
|
// ---------------------------------------------------------------------------
|
|
@@ -204,6 +228,16 @@ function attachCreateSubcommand(connections: Command): void {
|
|
|
204
228
|
"--credential <vault-key>",
|
|
205
229
|
"Vault credential name (required for --auth api_key)",
|
|
206
230
|
)
|
|
231
|
+
.option(
|
|
232
|
+
"--base-url <url>",
|
|
233
|
+
"Endpoint base URL (required for --provider openai-compatible)",
|
|
234
|
+
)
|
|
235
|
+
.option(
|
|
236
|
+
"--model <id>",
|
|
237
|
+
"Model id offered by this connection (repeatable; required for openai-compatible)",
|
|
238
|
+
collectRepeatable,
|
|
239
|
+
[] as string[],
|
|
240
|
+
)
|
|
207
241
|
.option("--json", "Output as JSON")
|
|
208
242
|
.action(
|
|
209
243
|
async (
|
|
@@ -212,6 +246,8 @@ function attachCreateSubcommand(connections: Command): void {
|
|
|
212
246
|
provider: string;
|
|
213
247
|
auth: string;
|
|
214
248
|
credential?: string;
|
|
249
|
+
baseUrl?: string;
|
|
250
|
+
model?: string[];
|
|
215
251
|
json?: boolean;
|
|
216
252
|
},
|
|
217
253
|
) => {
|
|
@@ -221,6 +257,14 @@ function attachCreateSubcommand(connections: Command): void {
|
|
|
221
257
|
return;
|
|
222
258
|
}
|
|
223
259
|
|
|
260
|
+
if (opts.provider === "openai-compatible" && !opts.baseUrl) {
|
|
261
|
+
writeCliError(
|
|
262
|
+
"--base-url is required when --provider openai-compatible",
|
|
263
|
+
opts.json,
|
|
264
|
+
);
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
|
|
224
268
|
const ipcResult = await cliIpcCall<ProviderConnection>(
|
|
225
269
|
"inference_provider_connections_create",
|
|
226
270
|
{
|
|
@@ -228,6 +272,7 @@ function attachCreateSubcommand(connections: Command): void {
|
|
|
228
272
|
name,
|
|
229
273
|
provider: opts.provider,
|
|
230
274
|
auth: authInput,
|
|
275
|
+
...buildCustomProviderFields(opts),
|
|
231
276
|
},
|
|
232
277
|
},
|
|
233
278
|
);
|
|
@@ -245,7 +290,8 @@ function attachCreateSubcommand(connections: Command): void {
|
|
|
245
290
|
);
|
|
246
291
|
} else {
|
|
247
292
|
process.stdout.write(
|
|
248
|
-
`Created connection "${conn.name}" (provider=${conn.provider}, auth=${formatAuth(conn.auth)})\n
|
|
293
|
+
`Created connection "${conn.name}" (provider=${conn.provider}, auth=${formatAuth(conn.auth)})\n` +
|
|
294
|
+
`Verify it works: point a profile's provider_connection at "${conn.name}", then run: assistant inference send --profile <profile> "Reply with OK"\n`,
|
|
249
295
|
);
|
|
250
296
|
}
|
|
251
297
|
},
|
|
@@ -265,11 +311,27 @@ function attachUpdateSubcommand(connections: Command): void {
|
|
|
265
311
|
"--credential <vault-key>",
|
|
266
312
|
"Vault credential name (required for --auth api_key)",
|
|
267
313
|
)
|
|
314
|
+
.option(
|
|
315
|
+
"--base-url <url>",
|
|
316
|
+
"Endpoint base URL (openai-compatible connections)",
|
|
317
|
+
)
|
|
318
|
+
.option(
|
|
319
|
+
"--model <id>",
|
|
320
|
+
"Model id offered by this connection (repeatable; openai-compatible)",
|
|
321
|
+
collectRepeatable,
|
|
322
|
+
[] as string[],
|
|
323
|
+
)
|
|
268
324
|
.option("--json", "Output as JSON")
|
|
269
325
|
.action(
|
|
270
326
|
async (
|
|
271
327
|
name: string,
|
|
272
|
-
opts: {
|
|
328
|
+
opts: {
|
|
329
|
+
auth: string;
|
|
330
|
+
credential?: string;
|
|
331
|
+
baseUrl?: string;
|
|
332
|
+
model?: string[];
|
|
333
|
+
json?: boolean;
|
|
334
|
+
},
|
|
273
335
|
) => {
|
|
274
336
|
const authInput = buildAuthInput(opts.auth, opts.credential);
|
|
275
337
|
if (typeof authInput === "string") {
|
|
@@ -281,7 +343,7 @@ function attachUpdateSubcommand(connections: Command): void {
|
|
|
281
343
|
"inference_provider_connections_update",
|
|
282
344
|
{
|
|
283
345
|
pathParams: { name },
|
|
284
|
-
body: { auth: authInput },
|
|
346
|
+
body: { auth: authInput, ...buildCustomProviderFields(opts) },
|
|
285
347
|
},
|
|
286
348
|
);
|
|
287
349
|
|
|
@@ -298,7 +360,8 @@ function attachUpdateSubcommand(connections: Command): void {
|
|
|
298
360
|
);
|
|
299
361
|
} else {
|
|
300
362
|
process.stdout.write(
|
|
301
|
-
`Updated connection "${name}" auth to ${formatAuth(conn.auth)}\n
|
|
363
|
+
`Updated connection "${name}" auth to ${formatAuth(conn.auth)}\n` +
|
|
364
|
+
`Verify it works: assistant inference send --profile <profile-using-this-connection> "Reply with OK"\n`,
|
|
302
365
|
);
|
|
303
366
|
}
|
|
304
367
|
},
|
|
@@ -498,8 +561,15 @@ Examples:
|
|
|
498
561
|
$ assistant inference providers connections get anthropic-managed
|
|
499
562
|
$ assistant inference providers connections create anthropic-personal \\
|
|
500
563
|
--provider anthropic --auth api_key --credential credential/anthropic/api_key
|
|
564
|
+
$ assistant inference providers connections create local-llm \\
|
|
565
|
+
--provider openai-compatible --auth none \\
|
|
566
|
+
--base-url http://localhost:1234/v1 --model my-model
|
|
501
567
|
$ assistant inference providers connections update anthropic-personal --auth platform
|
|
502
|
-
$ assistant inference providers connections delete anthropic-personal
|
|
568
|
+
$ assistant inference providers connections delete anthropic-personal
|
|
569
|
+
|
|
570
|
+
After creating or updating a connection, validate it with a live call through
|
|
571
|
+
a profile that uses it:
|
|
572
|
+
$ assistant inference send --profile <profile> "Reply with OK"`,
|
|
503
573
|
);
|
|
504
574
|
|
|
505
575
|
attachListSubcommand(connections);
|
|
@@ -509,4 +579,5 @@ Examples:
|
|
|
509
579
|
attachDeleteSubcommand(connections);
|
|
510
580
|
|
|
511
581
|
attachLoginChatgptSubcommand(providers);
|
|
582
|
+
attachDefaultProviderSubcommand(providers);
|
|
512
583
|
}
|
|
@@ -15,6 +15,9 @@ import { cliIpcCall } from "../../ipc/cli-client.js";
|
|
|
15
15
|
import { readStdinSync } from "../../util/read-stdin.js";
|
|
16
16
|
import { registerCommand } from "../lib/register-command.js";
|
|
17
17
|
import { log } from "../logger.js";
|
|
18
|
+
import { attachCallsitesSubcommand } from "./inference-callsites.js";
|
|
19
|
+
import { attachModelsSubcommand } from "./inference-models.js";
|
|
20
|
+
import { attachProfilesSubcommand } from "./inference-profiles.js";
|
|
18
21
|
import { attachProvidersSubcommand } from "./inference-providers.js";
|
|
19
22
|
import { attachSessionSubcommand } from "./inference-session.js";
|
|
20
23
|
|
|
@@ -248,6 +251,9 @@ Examples:
|
|
|
248
251
|
attachSendSubcommand(inference);
|
|
249
252
|
attachSessionSubcommand(inference);
|
|
250
253
|
attachProvidersSubcommand(inference);
|
|
254
|
+
attachModelsSubcommand(inference);
|
|
255
|
+
attachProfilesSubcommand(inference);
|
|
256
|
+
attachCallsitesSubcommand(inference);
|
|
251
257
|
},
|
|
252
258
|
});
|
|
253
259
|
|