@vellumai/assistant 0.12.4-staging.2 → 0.12.4-staging.3
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/package.json +1 -1
- package/src/__tests__/openai-responses-prompt-cache.test.ts +3 -1
- package/src/__tests__/pricing.test.ts +33 -0
- package/src/__tests__/skill-load-tool.test.ts +42 -11
- package/src/__tests__/skill-tool-factory.test.ts +50 -2
- package/src/__tests__/validate-input.test.ts +73 -0
- package/src/plugins/defaults/memory/substrate/skill-content.ts +1 -1
- package/src/plugins/defaults/memory/v3/__tests__/render-injection.test.ts +3 -0
- package/src/providers/__tests__/connection-model-compat.test.ts +2 -0
- package/src/providers/anthropic/client.ts +2 -2
- package/src/providers/model-catalog.ts +234 -0
- package/src/providers/openai/codex-models.ts +2 -0
- package/src/skills/validate-input.ts +75 -11
- package/src/tools/skills/execute.ts +1 -1
- package/src/tools/skills/format-input-schema.ts +15 -0
- package/src/tools/skills/load.ts +2 -26
- package/src/tools/skills/skill-tool-factory.ts +2 -1
package/package.json
CHANGED
|
@@ -446,7 +446,7 @@ describe("OpenAIResponsesProvider explicit prompt caching (GPT-5.6+)", () => {
|
|
|
446
446
|
expect(JSON.stringify(messages)).not.toContain("prompt_cache_breakpoint");
|
|
447
447
|
});
|
|
448
448
|
|
|
449
|
-
test("catalog flags GPT-5.6 and GPT-6
|
|
449
|
+
test("catalog flags GPT-5.6 and GPT-6 direct-openai rows", () => {
|
|
450
450
|
const openai = PROVIDER_CATALOG.find((p) => p.id === "openai");
|
|
451
451
|
const flagged = (openai?.models ?? [])
|
|
452
452
|
.filter((m) => m.supportsPromptCacheBreakpoints)
|
|
@@ -457,6 +457,8 @@ describe("OpenAIResponsesProvider explicit prompt caching (GPT-5.6+)", () => {
|
|
|
457
457
|
"gpt-5.6-sol",
|
|
458
458
|
"gpt-5.6-terra",
|
|
459
459
|
"gpt-6-astra",
|
|
460
|
+
"gpt-6-luna",
|
|
461
|
+
"gpt-6-sol",
|
|
460
462
|
]);
|
|
461
463
|
});
|
|
462
464
|
});
|
|
@@ -135,6 +135,39 @@ describe("resolvePricing", () => {
|
|
|
135
135
|
expect(result.estimatedCostUsd).toBeCloseTo(0.5 + 0.625 + 0.05, 10);
|
|
136
136
|
});
|
|
137
137
|
|
|
138
|
+
test("bills GPT-6 Sol at published short-context rates", () => {
|
|
139
|
+
const result = resolvePricingForUsage("openai", "gpt-6-sol", {
|
|
140
|
+
directInputTokens: 50_000,
|
|
141
|
+
outputTokens: 50_000,
|
|
142
|
+
cacheCreationInputTokens: 50_000,
|
|
143
|
+
cacheReadInputTokens: 50_000,
|
|
144
|
+
anthropicCacheCreation: null,
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
expect(result.pricingStatus).toBe("priced");
|
|
148
|
+
// 0.05M x $2 direct + 0.05M x $2.5 write + 0.05M x $0.2 read
|
|
149
|
+
// + 0.05M x $10 output.
|
|
150
|
+
expect(result.estimatedCostUsd).toBeCloseTo(0.1 + 0.125 + 0.01 + 0.5, 10);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test("bills GPT-6 Luna at the long-context tier above 272k", () => {
|
|
154
|
+
const result = resolvePricingForUsage("openai", "gpt-6-luna", {
|
|
155
|
+
directInputTokens: 500_000,
|
|
156
|
+
outputTokens: 1_000_000,
|
|
157
|
+
cacheCreationInputTokens: 500_000,
|
|
158
|
+
cacheReadInputTokens: 1_000_000,
|
|
159
|
+
anthropicCacheCreation: null,
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
expect(result.pricingStatus).toBe("priced");
|
|
163
|
+
// Tier rates: 0.5M x $0.2 direct + 0.5M x $0.25 write + 1M x $0.02 read
|
|
164
|
+
// + 1M x $0.75 output.
|
|
165
|
+
expect(result.estimatedCostUsd).toBeCloseTo(
|
|
166
|
+
0.1 + 0.125 + 0.02 + 0.75,
|
|
167
|
+
10,
|
|
168
|
+
);
|
|
169
|
+
});
|
|
170
|
+
|
|
138
171
|
test("bills GPT-6 Astra at the long-context tier above 272k", () => {
|
|
139
172
|
const result = resolvePricingForUsage("openai", "gpt-6-astra", {
|
|
140
173
|
directInputTokens: 500_000,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mkdirSync, symlinkSync, writeFileSync } from "node:fs";
|
|
1
|
+
import { mkdirSync, readFileSync, symlinkSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
4
4
|
|
|
@@ -781,16 +781,48 @@ describe("skill_load tool", () => {
|
|
|
781
781
|
expect(result.content).toContain("Deploy the application to production");
|
|
782
782
|
expect(result.content).toContain("Rollback to previous version");
|
|
783
783
|
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
784
|
+
const schemas = [
|
|
785
|
+
...result.content.matchAll(/```json\n([\s\S]*?)\n```/g),
|
|
786
|
+
].map((match) => JSON.parse(match[1]));
|
|
787
|
+
expect(schemas[0].required).toEqual(["environment"]);
|
|
788
|
+
expect(schemas[0].properties.environment).toEqual({
|
|
789
|
+
type: "string",
|
|
790
|
+
description: "Target environment",
|
|
791
|
+
});
|
|
792
|
+
expect(schemas[0].properties.force.type).toBe("boolean");
|
|
793
|
+
expect(schemas[1].required).toEqual(["version"]);
|
|
794
|
+
});
|
|
795
|
+
|
|
796
|
+
test("discloses the complete annotation mark schema before execution", async () => {
|
|
797
|
+
const manifest = JSON.parse(
|
|
798
|
+
readFileSync(
|
|
799
|
+
join(
|
|
800
|
+
import.meta.dir,
|
|
801
|
+
"../config/bundled-skills/screen-annotation/TOOLS.json",
|
|
802
|
+
),
|
|
803
|
+
"utf-8",
|
|
804
|
+
),
|
|
787
805
|
);
|
|
788
|
-
|
|
789
|
-
"-
|
|
806
|
+
writeSkill(
|
|
807
|
+
"annotation-schema",
|
|
808
|
+
"Annotation Schema",
|
|
809
|
+
"Point at a control",
|
|
810
|
+
"Name the control.",
|
|
790
811
|
);
|
|
791
|
-
|
|
792
|
-
|
|
812
|
+
writeToolsJson("annotation-schema", manifest.tools);
|
|
813
|
+
|
|
814
|
+
const result = await executeSkillLoad(
|
|
815
|
+
{ skill: "annotation-schema" },
|
|
816
|
+
"macos",
|
|
793
817
|
);
|
|
818
|
+
expect(result.isError).toBe(false);
|
|
819
|
+
const schemaMatch = result.content.match(/```json\n([\s\S]*?)\n```/);
|
|
820
|
+
expect(schemaMatch).not.toBeNull();
|
|
821
|
+
const schema = JSON.parse(schemaMatch![1]);
|
|
822
|
+
expect(schema).toEqual(manifest.tools[0].input_schema);
|
|
823
|
+
expect(schema.properties.marks.items.oneOf).toHaveLength(2);
|
|
824
|
+
expect(schema.properties.marks.items.properties.target.minLength).toBe(1);
|
|
825
|
+
expect(schema.properties.marks.items.properties.caption.maxLength).toBe(80);
|
|
794
826
|
});
|
|
795
827
|
|
|
796
828
|
test("skill without TOOLS.json does not include tool schemas section", async () => {
|
|
@@ -862,9 +894,8 @@ describe("skill_load tool", () => {
|
|
|
862
894
|
// The child skill's tool schemas should appear (#### level under ### Tools from …)
|
|
863
895
|
expect(result.content).toContain("#### child_action");
|
|
864
896
|
expect(result.content).toContain("A child tool action");
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
);
|
|
897
|
+
const schemaMatch = result.content.match(/```json\n([\s\S]*?)\n```/);
|
|
898
|
+
expect(JSON.parse(schemaMatch![1]).required).toEqual(["target"]);
|
|
868
899
|
expect(result.content).toContain(
|
|
869
900
|
"Use `skill_execute` to call these tools.",
|
|
870
901
|
);
|
|
@@ -4,7 +4,7 @@ import { tmpdir } from "node:os";
|
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
6
6
|
|
|
7
|
-
import type
|
|
7
|
+
import { getBundledSkillsDir, type SkillToolEntry } from "../config/skills.js";
|
|
8
8
|
import { RiskLevel } from "../permissions/types.js";
|
|
9
9
|
import { computeSkillVersionHash } from "../skills/version-hash.js";
|
|
10
10
|
import { resolveSubagentRole } from "../subagent/role-resolution.js";
|
|
@@ -261,6 +261,54 @@ describe("createSkillTool — unknown parameter validation", () => {
|
|
|
261
261
|
expect(result.content).toContain('Unknown parameter "bar"');
|
|
262
262
|
});
|
|
263
263
|
|
|
264
|
+
test.each([
|
|
265
|
+
{ description: "Filters" },
|
|
266
|
+
{ marks: [{ description: "Filters" }] },
|
|
267
|
+
{ marks: [{}] },
|
|
268
|
+
{ marks: [{ target: 42 }] },
|
|
269
|
+
{ marks: [{ x: 0.1, y: 0.2 }] },
|
|
270
|
+
{ marks: [{ target: "Filters", x: 0.1, y: 0.2, width: 0.3, height: 0.4 }] },
|
|
271
|
+
])(
|
|
272
|
+
"an annotation validation error provides enough schema for one corrected retry: %j",
|
|
273
|
+
async (invalid) => {
|
|
274
|
+
const skillDir = join(getBundledSkillsDir(), "screen-annotation");
|
|
275
|
+
const manifest = JSON.parse(
|
|
276
|
+
readFileSync(join(skillDir, "TOOLS.json"), "utf-8"),
|
|
277
|
+
);
|
|
278
|
+
const entry = manifest.tools.find(
|
|
279
|
+
(tool: SkillToolEntry) => tool.name === "screen_point_at",
|
|
280
|
+
);
|
|
281
|
+
const tool = createSkillTool(
|
|
282
|
+
entry,
|
|
283
|
+
skillDir,
|
|
284
|
+
computeSkillVersionHash(skillDir),
|
|
285
|
+
BUNDLED,
|
|
286
|
+
);
|
|
287
|
+
const calls: unknown[] = [];
|
|
288
|
+
const context = makeContext({
|
|
289
|
+
proxyToolResolver: async (name, input) => {
|
|
290
|
+
calls.push({ name, input });
|
|
291
|
+
return { content: "Drew 1 mark", isError: false };
|
|
292
|
+
},
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
const rejected = await tool.execute(invalid, context);
|
|
296
|
+
expect(rejected.isError).toBe(true);
|
|
297
|
+
expect(calls).toHaveLength(0);
|
|
298
|
+
expect(rejected.content).toContain("skill_load");
|
|
299
|
+
const schemaMatch = rejected.content.match(/```json\n([\s\S]*?)\n```/);
|
|
300
|
+
expect(schemaMatch).not.toBeNull();
|
|
301
|
+
expect(JSON.parse(schemaMatch![1])).toEqual(entry.input_schema);
|
|
302
|
+
|
|
303
|
+
const input = {
|
|
304
|
+
marks: [{ target: "Filters", caption: "Click Filters" }],
|
|
305
|
+
};
|
|
306
|
+
const corrected = await tool.execute(input, context);
|
|
307
|
+
expect(corrected.isError).toBe(false);
|
|
308
|
+
expect(calls).toEqual([{ name: "computer_use_point_at", input }]);
|
|
309
|
+
},
|
|
310
|
+
);
|
|
311
|
+
|
|
264
312
|
test("allows input with only known parameters", async () => {
|
|
265
313
|
const hash = computeSkillVersionHash(tempDir);
|
|
266
314
|
const tool = createSkillTool(
|
|
@@ -471,7 +519,7 @@ describe("createSkillTool — required/type/enum validation", () => {
|
|
|
471
519
|
|
|
472
520
|
expect(result.isError).toBe(true);
|
|
473
521
|
expect(result.content).toContain(
|
|
474
|
-
"auto_open must be a boolean
|
|
522
|
+
"auto_open must be a boolean: pass true or false as a JSON boolean, not a string",
|
|
475
523
|
);
|
|
476
524
|
});
|
|
477
525
|
|
|
@@ -6,6 +6,79 @@ import {
|
|
|
6
6
|
validateInputAgainstSchema,
|
|
7
7
|
} from "../skills/validate-input.js";
|
|
8
8
|
|
|
9
|
+
describe("nested input validation", () => {
|
|
10
|
+
const schema = {
|
|
11
|
+
properties: {
|
|
12
|
+
options: {
|
|
13
|
+
type: "object",
|
|
14
|
+
properties: { label: { type: "string" } },
|
|
15
|
+
required: ["label"],
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
test("reports nested required fields and types with their path", () => {
|
|
21
|
+
expect(
|
|
22
|
+
validateInputAgainstSchema("example", { options: {} }, schema),
|
|
23
|
+
).toEqual({
|
|
24
|
+
ok: false,
|
|
25
|
+
errors: ["options.label is required"],
|
|
26
|
+
});
|
|
27
|
+
expect(
|
|
28
|
+
validateInputAgainstSchema("example", { options: { label: 42 } }, schema),
|
|
29
|
+
).toEqual({
|
|
30
|
+
ok: false,
|
|
31
|
+
errors: ["options.label must be a string"],
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("preserves open nested objects and only rejects explicitly closed extra fields", () => {
|
|
36
|
+
const input = { options: { label: "Example", extra: true } };
|
|
37
|
+
expect(validateInputAgainstSchema("example", input, schema)).toEqual({
|
|
38
|
+
ok: true,
|
|
39
|
+
});
|
|
40
|
+
expect(
|
|
41
|
+
validateInputAgainstSchema("example", input, {
|
|
42
|
+
properties: {
|
|
43
|
+
options: {
|
|
44
|
+
...schema.properties.options,
|
|
45
|
+
additionalProperties: false,
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
}),
|
|
49
|
+
).toEqual({
|
|
50
|
+
ok: false,
|
|
51
|
+
errors: ['Unknown parameter "options.extra". Supported: "label"'],
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("does not treat a richer oneOf as a presence-only union", () => {
|
|
56
|
+
expect(
|
|
57
|
+
validateInputAgainstSchema(
|
|
58
|
+
"example",
|
|
59
|
+
{ options: { label: "Example" } },
|
|
60
|
+
{
|
|
61
|
+
properties: {
|
|
62
|
+
options: {
|
|
63
|
+
...schema.properties.options,
|
|
64
|
+
oneOf: [
|
|
65
|
+
{
|
|
66
|
+
required: ["label"],
|
|
67
|
+
properties: { label: { const: "Example" } },
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
required: ["label"],
|
|
71
|
+
properties: { label: { const: "Other" } },
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
),
|
|
78
|
+
).toEqual({ ok: true });
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
9
82
|
// ---------------------------------------------------------------------------
|
|
10
83
|
// required
|
|
11
84
|
// ---------------------------------------------------------------------------
|
|
@@ -34,7 +34,7 @@ export const DEFAULT_CARD_CHARS = 500;
|
|
|
34
34
|
* only, so a missing product is not evidence it is unsupported.
|
|
35
35
|
*/
|
|
36
36
|
export const SKILLS_INJECTION_CATALOG_HINT =
|
|
37
|
-
"Injected skills are only ones currently in the workspace. Run `assistant plugins search <name>` and `assistant skills search <name>` before concluding a given integration or skill is unsupported";
|
|
37
|
+
"These skill cards describe availability, not tool instructions. When skill_load is available, use it to load a skill's current instructions and input schemas before calling its tools; follow those schemas over remembered arguments. Injected skills are only ones currently in the workspace. Run `assistant plugins search <name>` and `assistant skills search <name>` before concluding a given integration or skill is unsupported";
|
|
38
38
|
|
|
39
39
|
/**
|
|
40
40
|
* Render the prose-style capability statement embedded into the unified
|
|
@@ -31,6 +31,9 @@ describe("renderInjectionBlockInner", () => {
|
|
|
31
31
|
expect(inner).toContain("assistant plugins search <name>");
|
|
32
32
|
expect(inner).toContain("assistant skills search <name>");
|
|
33
33
|
expect(inner).toContain("currently in the workspace");
|
|
34
|
+
expect(inner).toContain("When skill_load is available, use it");
|
|
35
|
+
expect(inner).toContain("before calling its tools");
|
|
36
|
+
expect(inner).toContain("over remembered arguments");
|
|
34
37
|
expect(inner.indexOf("# Skills")).toBeLessThan(inner.indexOf("# Skill:"));
|
|
35
38
|
expect(inner.startsWith(`${V3_INJECTION_HEADER}\n\n# Skills\n`)).toBe(true);
|
|
36
39
|
});
|
|
@@ -57,6 +57,8 @@ describe("isConnectionCompatibleWithModel", () => {
|
|
|
57
57
|
test("oauth_subscription connection is compatible with a Codex model", () => {
|
|
58
58
|
const conn = { auth: oauthAuth };
|
|
59
59
|
expect(isConnectionCompatibleWithModel(conn, "gpt-6-astra")).toBe(true);
|
|
60
|
+
expect(isConnectionCompatibleWithModel(conn, "gpt-6-sol")).toBe(true);
|
|
61
|
+
expect(isConnectionCompatibleWithModel(conn, "gpt-6-luna")).toBe(true);
|
|
60
62
|
expect(isConnectionCompatibleWithModel(conn, "gpt-5.6-sol")).toBe(true);
|
|
61
63
|
expect(isConnectionCompatibleWithModel(conn, "gpt-5.6-terra")).toBe(true);
|
|
62
64
|
expect(isConnectionCompatibleWithModel(conn, "gpt-5.6-luna")).toBe(true);
|
|
@@ -1004,7 +1004,7 @@ export class AnthropicProvider implements Provider {
|
|
|
1004
1004
|
const isHaiku = effectiveModel.includes("haiku");
|
|
1005
1005
|
// Effort support is per-model: Haiku and Sonnet 4.5 reject the param (see isEffortSupported).
|
|
1006
1006
|
const supportsEffort = isEffortSupported(effectiveModel);
|
|
1007
|
-
// opus-4-7 / opus-4-8 / opus-5 and sonnet-5 reject `temperature`,
|
|
1007
|
+
// opus-4-7 / opus-4-8 / opus-5 / opus-5-5 and sonnet-5 reject `temperature`,
|
|
1008
1008
|
// `top_p`, and `top_k` with a 400 "`temperature`/`top_p` is deprecated
|
|
1009
1009
|
// for this model" — model-wide, not effort-conditional (verified
|
|
1010
1010
|
// 2026-06-23). opus-4-6 / sonnet-4-6 / haiku-4-5 still accept them.
|
|
@@ -1016,7 +1016,7 @@ export class AnthropicProvider implements Provider {
|
|
|
1016
1016
|
// suffix is what matches.
|
|
1017
1017
|
const deprecatesSamplingParams =
|
|
1018
1018
|
/claude-opus-4-[78]\b/.test(effectiveModel) ||
|
|
1019
|
-
/claude-opus-5
|
|
1019
|
+
/claude-opus-5(-5)?\b/.test(effectiveModel) ||
|
|
1020
1020
|
/claude-sonnet-5\b/.test(effectiveModel) ||
|
|
1021
1021
|
/claude-fable-/.test(effectiveModel);
|
|
1022
1022
|
const mergedOutputConfig = {
|
|
@@ -245,6 +245,24 @@ const RAW_PROVIDER_CATALOG: ProviderCatalogEntry[] = [
|
|
|
245
245
|
cacheReadPer1mTokens: 1,
|
|
246
246
|
},
|
|
247
247
|
},
|
|
248
|
+
{
|
|
249
|
+
id: "claude-opus-5-5",
|
|
250
|
+
displayName: "Claude Opus 5.5",
|
|
251
|
+
contextWindowTokens: 1000000,
|
|
252
|
+
maxOutputTokens: 128000,
|
|
253
|
+
longContextPricingThresholdTokens: 200000,
|
|
254
|
+
supportsThinking: true,
|
|
255
|
+
adaptiveThinkingOnly: true,
|
|
256
|
+
supportsCaching: true,
|
|
257
|
+
supportsVision: true,
|
|
258
|
+
supportsToolUse: true,
|
|
259
|
+
pricing: {
|
|
260
|
+
inputPer1mTokens: 4,
|
|
261
|
+
outputPer1mTokens: 20,
|
|
262
|
+
cacheWritePer1mTokens: 5,
|
|
263
|
+
cacheReadPer1mTokens: 0.2,
|
|
264
|
+
},
|
|
265
|
+
},
|
|
248
266
|
{
|
|
249
267
|
id: "claude-opus-5",
|
|
250
268
|
displayName: "Claude Opus 5",
|
|
@@ -456,6 +474,66 @@ const RAW_PROVIDER_CATALOG: ProviderCatalogEntry[] = [
|
|
|
456
474
|
],
|
|
457
475
|
},
|
|
458
476
|
},
|
|
477
|
+
{
|
|
478
|
+
id: "gpt-6-sol",
|
|
479
|
+
displayName: "GPT-6 Sol",
|
|
480
|
+
contextWindowTokens: 1050000,
|
|
481
|
+
maxOutputTokens: 128000,
|
|
482
|
+
longContextPricingThresholdTokens:
|
|
483
|
+
OPENAI_LONG_CONTEXT_PRICING_THRESHOLD_TOKENS,
|
|
484
|
+
supportsThinking: true,
|
|
485
|
+
supportsCaching: true,
|
|
486
|
+
supportsVision: true,
|
|
487
|
+
supportsToolUse: true,
|
|
488
|
+
supportsPromptCacheBreakpoints: true,
|
|
489
|
+
maxEffort: "max",
|
|
490
|
+
supportedEfforts: ["low", "medium", "high", "xhigh", "max"],
|
|
491
|
+
pricing: {
|
|
492
|
+
inputPer1mTokens: 2,
|
|
493
|
+
outputPer1mTokens: 10,
|
|
494
|
+
cacheWritePer1mTokens: 2.5,
|
|
495
|
+
cacheReadPer1mTokens: 0.2,
|
|
496
|
+
tiers: [
|
|
497
|
+
{
|
|
498
|
+
inputTokenThreshold: OPENAI_LONG_CONTEXT_PRICING_THRESHOLD_TOKENS,
|
|
499
|
+
inputPer1mTokens: 4,
|
|
500
|
+
outputPer1mTokens: 15,
|
|
501
|
+
cacheWritePer1mTokens: 5,
|
|
502
|
+
cacheReadPer1mTokens: 0.4,
|
|
503
|
+
},
|
|
504
|
+
],
|
|
505
|
+
},
|
|
506
|
+
},
|
|
507
|
+
{
|
|
508
|
+
id: "gpt-6-luna",
|
|
509
|
+
displayName: "GPT-6 Luna",
|
|
510
|
+
contextWindowTokens: 1050000,
|
|
511
|
+
maxOutputTokens: 128000,
|
|
512
|
+
longContextPricingThresholdTokens:
|
|
513
|
+
OPENAI_LONG_CONTEXT_PRICING_THRESHOLD_TOKENS,
|
|
514
|
+
supportsThinking: true,
|
|
515
|
+
supportsCaching: true,
|
|
516
|
+
supportsVision: true,
|
|
517
|
+
supportsToolUse: true,
|
|
518
|
+
supportsPromptCacheBreakpoints: true,
|
|
519
|
+
maxEffort: "max",
|
|
520
|
+
supportedEfforts: ["low", "medium", "high", "xhigh", "max"],
|
|
521
|
+
pricing: {
|
|
522
|
+
inputPer1mTokens: 0.1,
|
|
523
|
+
outputPer1mTokens: 0.5,
|
|
524
|
+
cacheWritePer1mTokens: 0.125,
|
|
525
|
+
cacheReadPer1mTokens: 0.01,
|
|
526
|
+
tiers: [
|
|
527
|
+
{
|
|
528
|
+
inputTokenThreshold: OPENAI_LONG_CONTEXT_PRICING_THRESHOLD_TOKENS,
|
|
529
|
+
inputPer1mTokens: 0.2,
|
|
530
|
+
outputPer1mTokens: 0.75,
|
|
531
|
+
cacheWritePer1mTokens: 0.25,
|
|
532
|
+
cacheReadPer1mTokens: 0.02,
|
|
533
|
+
},
|
|
534
|
+
],
|
|
535
|
+
},
|
|
536
|
+
},
|
|
459
537
|
// GPT-5.6 family (Sol / Terra / Luna). cacheRead is the 90% cached-read
|
|
460
538
|
// discount; cacheWrite is the 1.25x-input rate GPT-5.6+ bills for
|
|
461
539
|
// prompt tokens written to the cache (reported as `cache_write_tokens`
|
|
@@ -1209,6 +1287,24 @@ const RAW_PROVIDER_CATALOG: ProviderCatalogEntry[] = [
|
|
|
1209
1287
|
cacheReadPer1mTokens: 1,
|
|
1210
1288
|
},
|
|
1211
1289
|
},
|
|
1290
|
+
{
|
|
1291
|
+
id: "anthropic/claude-opus-5.5",
|
|
1292
|
+
displayName: "Claude Opus 5.5",
|
|
1293
|
+
contextWindowTokens: 1000000,
|
|
1294
|
+
maxOutputTokens: 128000,
|
|
1295
|
+
longContextPricingThresholdTokens: 200000,
|
|
1296
|
+
supportsThinking: true,
|
|
1297
|
+
adaptiveThinkingOnly: true,
|
|
1298
|
+
supportsCaching: true,
|
|
1299
|
+
supportsVision: true,
|
|
1300
|
+
supportsToolUse: true,
|
|
1301
|
+
pricing: {
|
|
1302
|
+
inputPer1mTokens: 4,
|
|
1303
|
+
outputPer1mTokens: 20,
|
|
1304
|
+
cacheWritePer1mTokens: 5,
|
|
1305
|
+
cacheReadPer1mTokens: 0.2,
|
|
1306
|
+
},
|
|
1307
|
+
},
|
|
1212
1308
|
{
|
|
1213
1309
|
id: "anthropic/claude-opus-5",
|
|
1214
1310
|
displayName: "Claude Opus 5",
|
|
@@ -1431,6 +1527,126 @@ const RAW_PROVIDER_CATALOG: ProviderCatalogEntry[] = [
|
|
|
1431
1527
|
],
|
|
1432
1528
|
},
|
|
1433
1529
|
},
|
|
1530
|
+
{
|
|
1531
|
+
id: "openai/gpt-6-sol",
|
|
1532
|
+
displayName: "GPT-6 Sol",
|
|
1533
|
+
contextWindowTokens: 1050000,
|
|
1534
|
+
maxOutputTokens: 128000,
|
|
1535
|
+
longContextPricingThresholdTokens:
|
|
1536
|
+
OPENAI_LONG_CONTEXT_PRICING_THRESHOLD_TOKENS,
|
|
1537
|
+
supportsThinking: true,
|
|
1538
|
+
supportsCaching: true,
|
|
1539
|
+
supportsVision: true,
|
|
1540
|
+
supportsToolUse: true,
|
|
1541
|
+
supportsPromptCacheBreakpoints: true,
|
|
1542
|
+
maxEffort: "max",
|
|
1543
|
+
supportedEfforts: ["low", "medium", "high", "xhigh", "max"],
|
|
1544
|
+
pricing: {
|
|
1545
|
+
inputPer1mTokens: 2,
|
|
1546
|
+
outputPer1mTokens: 10,
|
|
1547
|
+
cacheWritePer1mTokens: 2.5,
|
|
1548
|
+
cacheReadPer1mTokens: 0.2,
|
|
1549
|
+
tiers: [
|
|
1550
|
+
{
|
|
1551
|
+
inputTokenThreshold: OPENAI_LONG_CONTEXT_PRICING_THRESHOLD_TOKENS,
|
|
1552
|
+
inputPer1mTokens: 4,
|
|
1553
|
+
outputPer1mTokens: 15,
|
|
1554
|
+
cacheWritePer1mTokens: 5,
|
|
1555
|
+
cacheReadPer1mTokens: 0.4,
|
|
1556
|
+
},
|
|
1557
|
+
],
|
|
1558
|
+
},
|
|
1559
|
+
},
|
|
1560
|
+
{
|
|
1561
|
+
id: "openai/gpt-6-sol-pro",
|
|
1562
|
+
displayName: "GPT-6 Sol Pro",
|
|
1563
|
+
contextWindowTokens: 1050000,
|
|
1564
|
+
maxOutputTokens: 128000,
|
|
1565
|
+
longContextPricingThresholdTokens:
|
|
1566
|
+
OPENAI_LONG_CONTEXT_PRICING_THRESHOLD_TOKENS,
|
|
1567
|
+
supportsThinking: true,
|
|
1568
|
+
supportsCaching: true,
|
|
1569
|
+
supportsVision: true,
|
|
1570
|
+
supportsToolUse: true,
|
|
1571
|
+
supportsPromptCacheBreakpoints: true,
|
|
1572
|
+
maxEffort: "max",
|
|
1573
|
+
supportedEfforts: ["low", "medium", "high", "xhigh", "max"],
|
|
1574
|
+
pricing: {
|
|
1575
|
+
inputPer1mTokens: 2,
|
|
1576
|
+
outputPer1mTokens: 10,
|
|
1577
|
+
cacheWritePer1mTokens: 2.5,
|
|
1578
|
+
cacheReadPer1mTokens: 0.2,
|
|
1579
|
+
tiers: [
|
|
1580
|
+
{
|
|
1581
|
+
inputTokenThreshold: OPENAI_LONG_CONTEXT_PRICING_THRESHOLD_TOKENS,
|
|
1582
|
+
inputPer1mTokens: 4,
|
|
1583
|
+
outputPer1mTokens: 15,
|
|
1584
|
+
cacheWritePer1mTokens: 5,
|
|
1585
|
+
cacheReadPer1mTokens: 0.4,
|
|
1586
|
+
},
|
|
1587
|
+
],
|
|
1588
|
+
},
|
|
1589
|
+
},
|
|
1590
|
+
{
|
|
1591
|
+
id: "openai/gpt-6-luna",
|
|
1592
|
+
displayName: "GPT-6 Luna",
|
|
1593
|
+
contextWindowTokens: 1050000,
|
|
1594
|
+
maxOutputTokens: 128000,
|
|
1595
|
+
longContextPricingThresholdTokens:
|
|
1596
|
+
OPENAI_LONG_CONTEXT_PRICING_THRESHOLD_TOKENS,
|
|
1597
|
+
supportsThinking: true,
|
|
1598
|
+
supportsCaching: true,
|
|
1599
|
+
supportsVision: true,
|
|
1600
|
+
supportsToolUse: true,
|
|
1601
|
+
supportsPromptCacheBreakpoints: true,
|
|
1602
|
+
maxEffort: "max",
|
|
1603
|
+
supportedEfforts: ["low", "medium", "high", "xhigh", "max"],
|
|
1604
|
+
pricing: {
|
|
1605
|
+
inputPer1mTokens: 0.1,
|
|
1606
|
+
outputPer1mTokens: 0.5,
|
|
1607
|
+
cacheWritePer1mTokens: 0.125,
|
|
1608
|
+
cacheReadPer1mTokens: 0.01,
|
|
1609
|
+
tiers: [
|
|
1610
|
+
{
|
|
1611
|
+
inputTokenThreshold: OPENAI_LONG_CONTEXT_PRICING_THRESHOLD_TOKENS,
|
|
1612
|
+
inputPer1mTokens: 0.2,
|
|
1613
|
+
outputPer1mTokens: 0.75,
|
|
1614
|
+
cacheWritePer1mTokens: 0.25,
|
|
1615
|
+
cacheReadPer1mTokens: 0.02,
|
|
1616
|
+
},
|
|
1617
|
+
],
|
|
1618
|
+
},
|
|
1619
|
+
},
|
|
1620
|
+
{
|
|
1621
|
+
id: "openai/gpt-6-luna-pro",
|
|
1622
|
+
displayName: "GPT-6 Luna Pro",
|
|
1623
|
+
contextWindowTokens: 1050000,
|
|
1624
|
+
maxOutputTokens: 128000,
|
|
1625
|
+
longContextPricingThresholdTokens:
|
|
1626
|
+
OPENAI_LONG_CONTEXT_PRICING_THRESHOLD_TOKENS,
|
|
1627
|
+
supportsThinking: true,
|
|
1628
|
+
supportsCaching: true,
|
|
1629
|
+
supportsVision: true,
|
|
1630
|
+
supportsToolUse: true,
|
|
1631
|
+
supportsPromptCacheBreakpoints: true,
|
|
1632
|
+
maxEffort: "max",
|
|
1633
|
+
supportedEfforts: ["low", "medium", "high", "xhigh", "max"],
|
|
1634
|
+
pricing: {
|
|
1635
|
+
inputPer1mTokens: 0.1,
|
|
1636
|
+
outputPer1mTokens: 0.5,
|
|
1637
|
+
cacheWritePer1mTokens: 0.125,
|
|
1638
|
+
cacheReadPer1mTokens: 0.01,
|
|
1639
|
+
tiers: [
|
|
1640
|
+
{
|
|
1641
|
+
inputTokenThreshold: OPENAI_LONG_CONTEXT_PRICING_THRESHOLD_TOKENS,
|
|
1642
|
+
inputPer1mTokens: 0.2,
|
|
1643
|
+
outputPer1mTokens: 0.75,
|
|
1644
|
+
cacheWritePer1mTokens: 0.25,
|
|
1645
|
+
cacheReadPer1mTokens: 0.02,
|
|
1646
|
+
},
|
|
1647
|
+
],
|
|
1648
|
+
},
|
|
1649
|
+
},
|
|
1434
1650
|
// GPT-5.6 family (Sol / Terra / Luna). The `*-pro` slugs are the same
|
|
1435
1651
|
// underlying models served with `reasoning.mode: pro` at identical
|
|
1436
1652
|
// rates. cacheWrite is the 1.25x-input rate GPT-5.6+ bills for prompt
|
|
@@ -2142,6 +2358,24 @@ const RAW_PROVIDER_CATALOG: ProviderCatalogEntry[] = [
|
|
|
2142
2358
|
cacheReadPer1mTokens: 1,
|
|
2143
2359
|
},
|
|
2144
2360
|
},
|
|
2361
|
+
{
|
|
2362
|
+
id: "anthropic/claude-opus-5.5",
|
|
2363
|
+
displayName: "Claude Opus 5.5",
|
|
2364
|
+
contextWindowTokens: 1000000,
|
|
2365
|
+
maxOutputTokens: 128000,
|
|
2366
|
+
longContextPricingThresholdTokens: 200000,
|
|
2367
|
+
supportsThinking: true,
|
|
2368
|
+
adaptiveThinkingOnly: true,
|
|
2369
|
+
supportsCaching: true,
|
|
2370
|
+
supportsVision: true,
|
|
2371
|
+
supportsToolUse: true,
|
|
2372
|
+
pricing: {
|
|
2373
|
+
inputPer1mTokens: 4,
|
|
2374
|
+
outputPer1mTokens: 20,
|
|
2375
|
+
cacheWritePer1mTokens: 5,
|
|
2376
|
+
cacheReadPer1mTokens: 0.2,
|
|
2377
|
+
},
|
|
2378
|
+
},
|
|
2145
2379
|
{
|
|
2146
2380
|
id: "anthropic/claude-opus-5",
|
|
2147
2381
|
displayName: "Claude Opus 5",
|
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Deliberate strict subset of JSON Schema: it covers only the keywords
|
|
5
5
|
* actually used by `assistant/src/config/bundled-skills/**\/TOOLS.json`
|
|
6
|
-
* (required / type / enum / items
|
|
7
|
-
* else (`$ref`, `oneOf`, `anyOf`,
|
|
8
|
-
* `
|
|
9
|
-
*
|
|
6
|
+
* (required / type / enum / nested properties and items / required-only oneOf)
|
|
7
|
+
* plus unknown-key detection. Anything else (`$ref`, richer `oneOf`, `anyOf`,
|
|
8
|
+
* `allOf`, `format`, `pattern`, `minimum`, `maximum`, object-shape
|
|
9
|
+
* `additionalProperties`, etc.) is skipped. Nested objects allow extra keys
|
|
10
|
+
* unless their schema explicitly closes them.
|
|
10
11
|
*
|
|
11
12
|
* Each error message is written to be agent-readable and self-correcting
|
|
12
13
|
* (e.g. `surface_id is required`, `mode must be one of "replace", "append"`).
|
|
@@ -313,6 +314,15 @@ export function validateInputAgainstSchema(
|
|
|
313
314
|
_toolName: string,
|
|
314
315
|
input: Record<string, unknown>,
|
|
315
316
|
schema: Record<string, unknown> | undefined,
|
|
317
|
+
): InputValidationResult {
|
|
318
|
+
return validateObjectInput(input, schema, "", true);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function validateObjectInput(
|
|
322
|
+
input: Record<string, unknown>,
|
|
323
|
+
schema: Record<string, unknown> | undefined,
|
|
324
|
+
path: string,
|
|
325
|
+
rejectUnknown: boolean,
|
|
316
326
|
): InputValidationResult {
|
|
317
327
|
// Skip when there's no schema or no properties block — matches today's
|
|
318
328
|
// lenient behaviour for tools that declare only `{ type: "object" }`.
|
|
@@ -338,17 +348,46 @@ export function validateInputAgainstSchema(
|
|
|
338
348
|
continue;
|
|
339
349
|
}
|
|
340
350
|
if (!(key in input)) {
|
|
341
|
-
errors.push(`${key} is required`);
|
|
351
|
+
errors.push(`${path}${key} is required`);
|
|
342
352
|
}
|
|
343
353
|
}
|
|
344
354
|
}
|
|
345
355
|
|
|
356
|
+
// Only enforce alternatives whose complete contract is field presence.
|
|
357
|
+
// Richer unions stay permissive because unsupported keywords can decide
|
|
358
|
+
// which branch matches.
|
|
359
|
+
const alternatives = schema.oneOf;
|
|
360
|
+
if (
|
|
361
|
+
Array.isArray(alternatives) &&
|
|
362
|
+
alternatives.length > 0 &&
|
|
363
|
+
alternatives.every(
|
|
364
|
+
(branch) =>
|
|
365
|
+
isPlainObject(branch) &&
|
|
366
|
+
Object.keys(branch).every(
|
|
367
|
+
(key) => key === "required" || key === "description",
|
|
368
|
+
) &&
|
|
369
|
+
Array.isArray(branch.required) &&
|
|
370
|
+
branch.required.every((key: unknown) => typeof key === "string"),
|
|
371
|
+
)
|
|
372
|
+
) {
|
|
373
|
+
const choices = alternatives as { required: string[] }[];
|
|
374
|
+
if (
|
|
375
|
+
choices.filter((branch) => branch.required.every((key) => key in input))
|
|
376
|
+
.length !== 1
|
|
377
|
+
) {
|
|
378
|
+
errors.push(
|
|
379
|
+
`${path ? path.slice(0, -1) : "input"} must match exactly one required field set: ${choices.map((branch) => quoteList(branch.required)).join(" or ")}`,
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
346
384
|
// 2. Per-property checks: type, enum, items.type.
|
|
347
|
-
for (const [
|
|
348
|
-
if (!(
|
|
385
|
+
for (const [property, rawSubSchema] of Object.entries(properties)) {
|
|
386
|
+
if (!(property in input)) {
|
|
349
387
|
continue;
|
|
350
388
|
}
|
|
351
|
-
const
|
|
389
|
+
const key = `${path}${property}`;
|
|
390
|
+
const value = input[property];
|
|
352
391
|
// Skip type-checking for absent values; presence is enforced by the
|
|
353
392
|
// `required` check above. Note: `null` IS a present value and is
|
|
354
393
|
// type-checked below — only schemas that explicitly opt in to null via
|
|
@@ -381,13 +420,25 @@ export function validateInputAgainstSchema(
|
|
|
381
420
|
if (!matchesType(value, type)) {
|
|
382
421
|
errors.push(
|
|
383
422
|
type === "boolean" && typeof value === "string"
|
|
384
|
-
? `${key} must be a boolean
|
|
423
|
+
? `${key} must be a boolean: pass true or false as a JSON boolean, not a string`
|
|
385
424
|
: `${key} must be ${typeArticle(type)} ${type}`,
|
|
386
425
|
);
|
|
387
426
|
// No point checking enum/items if the base type is wrong.
|
|
388
427
|
continue;
|
|
389
428
|
}
|
|
390
429
|
|
|
430
|
+
if (type === "object" && isPlainObject(value)) {
|
|
431
|
+
const nested = validateObjectInput(
|
|
432
|
+
value,
|
|
433
|
+
rawSubSchema,
|
|
434
|
+
`${key}.`,
|
|
435
|
+
rawSubSchema.additionalProperties === false,
|
|
436
|
+
);
|
|
437
|
+
if (!nested.ok) {
|
|
438
|
+
errors.push(...nested.errors);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
391
442
|
// 2a. Enum (string enums are the only shape used today).
|
|
392
443
|
const enumValues = rawSubSchema.enum;
|
|
393
444
|
if (Array.isArray(enumValues) && enumValues.length > 0) {
|
|
@@ -412,6 +463,17 @@ export function validateInputAgainstSchema(
|
|
|
412
463
|
errors.push(
|
|
413
464
|
`${key}[${index}] must be ${typeArticle(itemTypeName)} ${itemTypeName}`,
|
|
414
465
|
);
|
|
466
|
+
} else if (itemTypeName === "object" && isPlainObject(element)) {
|
|
467
|
+
const itemSchema = rawSubSchema.items as Record<string, unknown>;
|
|
468
|
+
const nested = validateObjectInput(
|
|
469
|
+
element,
|
|
470
|
+
itemSchema,
|
|
471
|
+
`${key}[${index}].`,
|
|
472
|
+
itemSchema.additionalProperties === false,
|
|
473
|
+
);
|
|
474
|
+
if (!nested.ok) {
|
|
475
|
+
errors.push(...nested.errors);
|
|
476
|
+
}
|
|
415
477
|
}
|
|
416
478
|
});
|
|
417
479
|
}
|
|
@@ -420,10 +482,12 @@ export function validateInputAgainstSchema(
|
|
|
420
482
|
}
|
|
421
483
|
|
|
422
484
|
// 3. Unknown keys — parity with the previous `validateNoUnknownParams`.
|
|
423
|
-
const unknownKeys =
|
|
485
|
+
const unknownKeys = rejectUnknown
|
|
486
|
+
? Object.keys(input).filter((k) => !knownKeySet.has(k))
|
|
487
|
+
: [];
|
|
424
488
|
for (const key of unknownKeys) {
|
|
425
489
|
errors.push(
|
|
426
|
-
`Unknown parameter "${key}". Supported: ${quoteList(knownKeys)}`,
|
|
490
|
+
`Unknown parameter "${path}${key}". Supported: ${quoteList(knownKeys)}`,
|
|
427
491
|
);
|
|
428
492
|
}
|
|
429
493
|
|
|
@@ -178,7 +178,7 @@ export function augmentSkillExecuteError(
|
|
|
178
178
|
export const skillExecuteTool = {
|
|
179
179
|
name: "skill_execute",
|
|
180
180
|
description:
|
|
181
|
-
"Execute a tool provided by a loaded skill.
|
|
181
|
+
"Execute a tool provided by a loaded skill. When skill_load is available, call it for the owning skill before first use, even if a capability card says it is available. Use the current instructions and input schema over remembered arguments. Use this instead of calling skill tools directly. For browser automation, use the `assistant browser` CLI commands instead.",
|
|
182
182
|
category: "skills",
|
|
183
183
|
executionTarget: "sandbox",
|
|
184
184
|
defaultRiskLevel: RiskLevel.Low,
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { getWorkspaceDirDisplay } from "../../util/platform.js";
|
|
2
|
+
|
|
3
|
+
/** Preserve nested fields and alternatives when disclosing a skill's input. */
|
|
4
|
+
export function formatSkillInputSchema(schema: object): string {
|
|
5
|
+
const workspaceDir = getWorkspaceDirDisplay();
|
|
6
|
+
const json = JSON.stringify(
|
|
7
|
+
schema,
|
|
8
|
+
(_key, value: unknown) =>
|
|
9
|
+
typeof value === "string"
|
|
10
|
+
? value.replaceAll("{workspaceDir}", workspaceDir)
|
|
11
|
+
: value,
|
|
12
|
+
2,
|
|
13
|
+
);
|
|
14
|
+
return `Input schema for \`skill_execute.input\`:\n\`\`\`json\n${json}\n\`\`\``;
|
|
15
|
+
}
|
package/src/tools/skills/load.ts
CHANGED
|
@@ -35,6 +35,7 @@ import type {
|
|
|
35
35
|
ToolDefinition,
|
|
36
36
|
ToolExecutionResult,
|
|
37
37
|
} from "../types.js";
|
|
38
|
+
import { formatSkillInputSchema } from "./format-input-schema.js";
|
|
38
39
|
|
|
39
40
|
/** Skill sources eligible for inline command expansion in v1. */
|
|
40
41
|
const INLINE_COMMAND_ELIGIBLE_SOURCES = new Set([
|
|
@@ -123,32 +124,7 @@ function formatToolSchemas(
|
|
|
123
124
|
tool.description.replaceAll("{workspaceDir}", getWorkspaceDirDisplay()),
|
|
124
125
|
);
|
|
125
126
|
|
|
126
|
-
|
|
127
|
-
const properties = schema.properties as
|
|
128
|
-
| Record<string, Record<string, unknown>>
|
|
129
|
-
| undefined;
|
|
130
|
-
if (properties && Object.keys(properties).length > 0) {
|
|
131
|
-
const requiredSet = new Set<string>(
|
|
132
|
-
Array.isArray(schema.required) ? (schema.required as string[]) : [],
|
|
133
|
-
);
|
|
134
|
-
|
|
135
|
-
lines.push("Parameters:");
|
|
136
|
-
for (const [paramName, paramDef] of Object.entries(properties)) {
|
|
137
|
-
const paramType =
|
|
138
|
-
typeof paramDef.type === "string" ? paramDef.type : "any";
|
|
139
|
-
const requiredLabel = requiredSet.has(paramName)
|
|
140
|
-
? "required"
|
|
141
|
-
: "optional";
|
|
142
|
-
const descPart =
|
|
143
|
-
typeof paramDef.description === "string"
|
|
144
|
-
? `: ${paramDef.description.replaceAll("{workspaceDir}", getWorkspaceDirDisplay())}`
|
|
145
|
-
: "";
|
|
146
|
-
lines.push(
|
|
147
|
-
`- ${paramName} (${paramType}, ${requiredLabel})${descPart}`,
|
|
148
|
-
);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
127
|
+
lines.push(formatSkillInputSchema(tool.input_schema));
|
|
152
128
|
lines.push("");
|
|
153
129
|
}
|
|
154
130
|
|
|
@@ -15,6 +15,7 @@ import { bundledToolInputMisuseMessage } from "../shared/input-misuse.js";
|
|
|
15
15
|
import { bundledToolInputRepairs } from "../shared/input-repairs.js";
|
|
16
16
|
import type { ExecutionTarget } from "../tool-types.js";
|
|
17
17
|
import type { Tool, ToolContext, ToolExecutionResult } from "../types.js";
|
|
18
|
+
import { formatSkillInputSchema } from "./format-input-schema.js";
|
|
18
19
|
import { runSkillToolScript } from "./skill-script-runner.js";
|
|
19
20
|
|
|
20
21
|
const riskMap: Record<SkillToolEntry["risk"], RiskLevel> = {
|
|
@@ -94,7 +95,7 @@ export function createSkillTool(
|
|
|
94
95
|
return {
|
|
95
96
|
content:
|
|
96
97
|
misuse ??
|
|
97
|
-
`Invalid input for tool "${entry.name}": ${validation.errors.join("; ")}.
|
|
98
|
+
`Invalid input for tool "${entry.name}": ${validation.errors.join("; ")}. If skill_load is available, load the owning skill for its current instructions. Retry with arguments matching this schema.\n\n${formatSkillInputSchema(entry.input_schema)}`,
|
|
98
99
|
isError: true,
|
|
99
100
|
};
|
|
100
101
|
}
|