@vellumai/assistant 0.8.12-staging.1 → 0.8.12
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 +507 -0
- package/package.json +1 -1
- package/src/__tests__/llm-catalog-parity.test.ts +16 -0
- package/src/__tests__/log-export-workspace.test.ts +468 -3
- package/src/__tests__/secret-fixtures.ts +20 -0
- package/src/__tests__/tool-approval-handler.test.ts +85 -0
- package/src/__tests__/tool-audit-listener.test.ts +86 -0
- package/src/__tests__/workspace-migration-100-upgrade-quality-profile-to-fable-5.test.ts +174 -0
- package/src/__tests__/workspace-migration-101-upgrade-balanced-economy-to-minimax-m3.test.ts +162 -0
- package/src/acp/__tests__/agent-process.test.ts +315 -2
- package/src/acp/__tests__/prepare-agent-env.test.ts +79 -5
- package/src/acp/agent-process.ts +163 -34
- package/src/acp/prepare-agent-env.ts +55 -15
- package/src/bundler/app-compiler.ts +8 -0
- package/src/cli/lib/__tests__/upgrade-plugin.test.ts +10 -4
- package/src/cli/lib/upgrade-plugin.ts +13 -7
- package/src/config/seed-inference-profiles.ts +4 -8
- package/src/events/tool-audit-listener.ts +40 -9
- package/src/providers/__tests__/unparseable-tool-args.test.ts +53 -0
- package/src/providers/model-catalog.ts +28 -0
- package/src/providers/model-intents.ts +1 -1
- package/src/providers/openai/chat-completions-provider.ts +2 -1
- package/src/providers/openai/responses-provider.ts +2 -1
- package/src/providers/unparseable-tool-args.ts +56 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +132 -0
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +347 -0
- package/src/runtime/routes/conversation-query-routes.ts +79 -4
- package/src/runtime/routes/log-export-routes.ts +143 -96
- package/src/runtime/routes/plugins-routes.ts +359 -0
- package/src/runtime/routes/redact-staged-export.ts +259 -0
- package/src/security/redact-json.ts +61 -0
- package/src/tools/tool-approval-handler.ts +31 -0
- package/src/workspace/migrations/100-upgrade-quality-profile-to-fable-5.ts +86 -0
- package/src/workspace/migrations/101-upgrade-balanced-economy-to-minimax-m3.ts +70 -0
- package/src/workspace/migrations/registry.ts +4 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON-aware secret redaction: walk a parsed JSON value and run
|
|
3
|
+
* `redactSecrets()` over every string leaf (object keys included).
|
|
4
|
+
*
|
|
5
|
+
* The redaction marker (`<redacted type="..." />`) contains double quotes,
|
|
6
|
+
* so splicing it into an already-serialized JSON document corrupts any
|
|
7
|
+
* string it lands inside. Callers that need to keep serialized JSON valid
|
|
8
|
+
* must redact the string leaves of the parsed value and re-stringify,
|
|
9
|
+
* rather than redacting the serialized text.
|
|
10
|
+
*
|
|
11
|
+
* Shared by the export-time staged-file sweep (`redact-staged-export.ts`)
|
|
12
|
+
* and the tool-audit listener (`tool-audit-listener.ts`). The hot-path pino
|
|
13
|
+
* log serializer (`util/log-redact.ts`) intentionally does not use this
|
|
14
|
+
* module.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { redactSecrets } from "./secret-scanner.js";
|
|
18
|
+
|
|
19
|
+
export interface RedactedJsonValue {
|
|
20
|
+
/** Redacted copy of the input (the input itself is never mutated). */
|
|
21
|
+
value: unknown;
|
|
22
|
+
/** True when at least one string leaf was changed by redaction. */
|
|
23
|
+
changed: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface RedactionState {
|
|
27
|
+
changed: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Redact secrets in every string leaf (keys included) of a parsed JSON
|
|
32
|
+
* value. Returns the redacted copy plus a `changed` flag so callers can
|
|
33
|
+
* keep unchanged content byte-identical (no gratuitous re-serialization).
|
|
34
|
+
*/
|
|
35
|
+
export function redactJsonStringLeaves(value: unknown): RedactedJsonValue {
|
|
36
|
+
const state: RedactionState = { changed: false };
|
|
37
|
+
return { value: walk(value, state), changed: state.changed };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function walk(value: unknown, state: RedactionState): unknown {
|
|
41
|
+
if (typeof value === "string") {
|
|
42
|
+
return redactString(value, state);
|
|
43
|
+
}
|
|
44
|
+
if (Array.isArray(value)) {
|
|
45
|
+
return value.map((item) => walk(item, state));
|
|
46
|
+
}
|
|
47
|
+
if (value !== null && typeof value === "object") {
|
|
48
|
+
return Object.fromEntries(
|
|
49
|
+
Object.entries(value).map(
|
|
50
|
+
([key, val]) => [redactString(key, state), walk(val, state)] as const,
|
|
51
|
+
),
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
return value;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function redactString(value: string, state: RedactionState): string {
|
|
58
|
+
const redacted = redactSecrets(value);
|
|
59
|
+
if (redacted !== value) state.changed = true;
|
|
60
|
+
return redacted;
|
|
61
|
+
}
|
|
@@ -5,6 +5,10 @@ import {
|
|
|
5
5
|
getCanonicalGuardianRequest,
|
|
6
6
|
updateCanonicalGuardianRequest,
|
|
7
7
|
} from "../memory/canonical-guardian-store.js";
|
|
8
|
+
import {
|
|
9
|
+
isUnparseableToolArgs,
|
|
10
|
+
unparseableToolArgsMessage,
|
|
11
|
+
} from "../providers/unparseable-tool-args.js";
|
|
8
12
|
import { isUntrustedTrustClass } from "../runtime/actor-trust-resolver.js";
|
|
9
13
|
import { createOrReuseToolGrantRequest } from "../runtime/tool-grant-request-helper.js";
|
|
10
14
|
import { redactSecrets } from "../security/secret-scanner.js";
|
|
@@ -241,6 +245,33 @@ export class ToolApprovalHandler {
|
|
|
241
245
|
};
|
|
242
246
|
}
|
|
243
247
|
|
|
248
|
+
// Reject tool calls whose arguments failed JSON parsing in the provider
|
|
249
|
+
// layer (wrapped under the `_raw` marker). Executing with the marker
|
|
250
|
+
// object would feed garbage input to the tool — and worse, a tool that
|
|
251
|
+
// tolerates missing fields can "succeed" (e.g. ui_show creating a
|
|
252
|
+
// typeless surface), so the model never learns its arguments were
|
|
253
|
+
// mangled. Fail loudly instead so the model retries.
|
|
254
|
+
if (isUnparseableToolArgs(input)) {
|
|
255
|
+
const msg = unparseableToolArgsMessage(name, input._raw);
|
|
256
|
+
const durationMs = Date.now() - startTime;
|
|
257
|
+
emitLifecycleEvent({
|
|
258
|
+
type: "error",
|
|
259
|
+
toolName: name,
|
|
260
|
+
executionTarget,
|
|
261
|
+
input,
|
|
262
|
+
workingDir: context.workingDir,
|
|
263
|
+
conversationId: context.conversationId,
|
|
264
|
+
requestId: context.requestId,
|
|
265
|
+
riskLevel,
|
|
266
|
+
decision: "error",
|
|
267
|
+
durationMs,
|
|
268
|
+
errorMessage: msg,
|
|
269
|
+
isExpected: true,
|
|
270
|
+
errorCategory: "tool_failure",
|
|
271
|
+
});
|
|
272
|
+
return { allowed: false, result: { content: msg, isError: true } };
|
|
273
|
+
}
|
|
274
|
+
|
|
244
275
|
// Reject tool invocations targeting guardian control-plane endpoints from non-guardian actors.
|
|
245
276
|
const guardianCheck = enforceVerificationControlPlanePolicy(
|
|
246
277
|
name,
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
import type { WorkspaceMigration } from "./types.js";
|
|
5
|
+
|
|
6
|
+
// Upgrade quality-optimized profiles to Claude Fable 5.
|
|
7
|
+
//
|
|
8
|
+
// The quality-optimized model intent moved to claude-fable-5 (#34498), but
|
|
9
|
+
// the intent map is only consulted when a profile is seeded: off-platform
|
|
10
|
+
// installs reseed managed profiles on every boot, while on-platform
|
|
11
|
+
// workspaces keep the profile they were hatched with — the seeder skips
|
|
12
|
+
// existing profiles there, and the hatch overlay is consumed exactly once.
|
|
13
|
+
// Existing platform assistants are therefore stuck on whichever model the
|
|
14
|
+
// quality intent pointed at when they hatched (claude-opus-4-7 or
|
|
15
|
+
// claude-opus-4-8) until a migration rewrites it.
|
|
16
|
+
//
|
|
17
|
+
// Only the managed quality-optimized profile is touched — the user-owned
|
|
18
|
+
// custom-quality-optimized copy is theirs to manage. Within it, only a model
|
|
19
|
+
// still on a previous quality-intent default is upgraded — matching by model
|
|
20
|
+
// value also covers the OpenRouter-prefixed ids. A profile whose model the
|
|
21
|
+
// user changed to anything else is left untouched.
|
|
22
|
+
|
|
23
|
+
const TARGET_PROFILES = ["quality-optimized"];
|
|
24
|
+
|
|
25
|
+
const MODEL_UPGRADES: Record<string, string> = {
|
|
26
|
+
"claude-opus-4-7": "claude-fable-5",
|
|
27
|
+
"claude-opus-4-8": "claude-fable-5",
|
|
28
|
+
"anthropic/claude-opus-4.7": "anthropic/claude-fable-5",
|
|
29
|
+
"anthropic/claude-opus-4.8": "anthropic/claude-fable-5",
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const upgradeQualityProfileToFable5Migration: WorkspaceMigration = {
|
|
33
|
+
id: "100-upgrade-quality-profile-to-fable-5",
|
|
34
|
+
description:
|
|
35
|
+
"Upgrade the managed quality-optimized profile from Opus 4.7/4.8 to Claude Fable 5",
|
|
36
|
+
run(workspaceDir: string): void {
|
|
37
|
+
const configPath = join(workspaceDir, "config.json");
|
|
38
|
+
if (!existsSync(configPath)) return;
|
|
39
|
+
|
|
40
|
+
let config: Record<string, unknown>;
|
|
41
|
+
try {
|
|
42
|
+
const raw = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
43
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return;
|
|
44
|
+
config = raw as Record<string, unknown>;
|
|
45
|
+
} catch {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const llm = readObject(config.llm);
|
|
50
|
+
if (llm === null) return;
|
|
51
|
+
|
|
52
|
+
const profiles = readObject(llm.profiles);
|
|
53
|
+
if (profiles === null) return;
|
|
54
|
+
|
|
55
|
+
let changed = false;
|
|
56
|
+
|
|
57
|
+
for (const name of TARGET_PROFILES) {
|
|
58
|
+
const profile = readObject(profiles[name]);
|
|
59
|
+
if (profile === null) continue;
|
|
60
|
+
if (typeof profile.model !== "string") continue;
|
|
61
|
+
|
|
62
|
+
const upgraded = MODEL_UPGRADES[profile.model];
|
|
63
|
+
if (upgraded === undefined) continue;
|
|
64
|
+
|
|
65
|
+
profile.model = upgraded;
|
|
66
|
+
profiles[name] = profile;
|
|
67
|
+
changed = true;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (changed) {
|
|
71
|
+
llm.profiles = profiles;
|
|
72
|
+
config.llm = llm;
|
|
73
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
down(_workspaceDir: string): void {
|
|
77
|
+
// Forward-only.
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
function readObject(value: unknown): Record<string, unknown> | null {
|
|
82
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
return value as Record<string, unknown>;
|
|
86
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
import type { WorkspaceMigration } from "./types.js";
|
|
5
|
+
|
|
6
|
+
// Switch the Balanced Economy profile from Kimi K2.6 to MiniMax M3.
|
|
7
|
+
//
|
|
8
|
+
// The Fireworks balanced model intent moved to MiniMax M3 (#34726), but the
|
|
9
|
+
// intent map is only consulted when a profile is seeded: off-platform
|
|
10
|
+
// installs reseed managed profiles on every boot, while on-platform
|
|
11
|
+
// workspaces keep the profile they were hatched with — the seeder skips
|
|
12
|
+
// existing profiles there. Existing platform assistants would otherwise stay
|
|
13
|
+
// on Kimi K2.6 (including the now-dropped `suppress-cjk` logit-bias preset)
|
|
14
|
+
// until a migration rewrites the profile.
|
|
15
|
+
//
|
|
16
|
+
// Only the managed balanced-economy profile still pointing at the old
|
|
17
|
+
// default model is touched — a profile whose model the user changed is left
|
|
18
|
+
// alone. The rewrite mirrors the new seed template: MiniMax M3, 32K max
|
|
19
|
+
// output tokens, no logit-bias preset.
|
|
20
|
+
|
|
21
|
+
const OLD_MODEL = "accounts/fireworks/models/kimi-k2p6";
|
|
22
|
+
const NEW_MODEL = "accounts/fireworks/models/minimax-m3";
|
|
23
|
+
|
|
24
|
+
export const upgradeBalancedEconomyToMinimaxM3Migration: WorkspaceMigration = {
|
|
25
|
+
id: "101-upgrade-balanced-economy-to-minimax-m3",
|
|
26
|
+
description:
|
|
27
|
+
"Switch the managed Balanced Economy profile from Kimi K2.6 to MiniMax M3",
|
|
28
|
+
run(workspaceDir: string): void {
|
|
29
|
+
const configPath = join(workspaceDir, "config.json");
|
|
30
|
+
if (!existsSync(configPath)) return;
|
|
31
|
+
|
|
32
|
+
let config: Record<string, unknown>;
|
|
33
|
+
try {
|
|
34
|
+
const raw = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
35
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return;
|
|
36
|
+
config = raw as Record<string, unknown>;
|
|
37
|
+
} catch {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const llm = readObject(config.llm);
|
|
42
|
+
if (llm === null) return;
|
|
43
|
+
|
|
44
|
+
const profiles = readObject(llm.profiles);
|
|
45
|
+
if (profiles === null) return;
|
|
46
|
+
|
|
47
|
+
const profile = readObject(profiles["balanced-economy"]);
|
|
48
|
+
if (profile === null || profile.model !== OLD_MODEL) return;
|
|
49
|
+
|
|
50
|
+
profile.model = NEW_MODEL;
|
|
51
|
+
profile.description =
|
|
52
|
+
"Strong open model (MiniMax M3) at a lower price point";
|
|
53
|
+
profile.maxTokens = 32000;
|
|
54
|
+
delete profile.logitBias;
|
|
55
|
+
profiles["balanced-economy"] = profile;
|
|
56
|
+
llm.profiles = profiles;
|
|
57
|
+
config.llm = llm;
|
|
58
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
59
|
+
},
|
|
60
|
+
down(_workspaceDir: string): void {
|
|
61
|
+
// Forward-only.
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
function readObject(value: unknown): Record<string, unknown> | null {
|
|
66
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
return value as Record<string, unknown>;
|
|
70
|
+
}
|
|
@@ -97,6 +97,8 @@ import { reduceQualityProfileEffortMigration } from "./096-reduce-quality-profil
|
|
|
97
97
|
import { enableAdaptiveThinkingManagedProfilesMigration } from "./097-enable-adaptive-thinking-managed-profiles.js";
|
|
98
98
|
import { removeStaleUpdatesBulletinFileMigration } from "./098-remove-stale-updates-bulletin-file.js";
|
|
99
99
|
import { disableCacheOneShotCallsitesMigration } from "./099-disable-cache-one-shot-callsites.js";
|
|
100
|
+
import { upgradeQualityProfileToFable5Migration } from "./100-upgrade-quality-profile-to-fable-5.js";
|
|
101
|
+
import { upgradeBalancedEconomyToMinimaxM3Migration } from "./101-upgrade-balanced-economy-to-minimax-m3.js";
|
|
100
102
|
import { migrateToWorkspaceVolumeMigration } from "./migrate-to-workspace-volume.js";
|
|
101
103
|
import type { WorkspaceMigration } from "./types.js";
|
|
102
104
|
|
|
@@ -205,4 +207,6 @@ export const WORKSPACE_MIGRATIONS: WorkspaceMigration[] = [
|
|
|
205
207
|
enableAdaptiveThinkingManagedProfilesMigration,
|
|
206
208
|
removeStaleUpdatesBulletinFileMigration,
|
|
207
209
|
disableCacheOneShotCallsitesMigration,
|
|
210
|
+
upgradeQualityProfileToFable5Migration,
|
|
211
|
+
upgradeBalancedEconomyToMinimaxM3Migration,
|
|
208
212
|
];
|