Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/opencode/src/session/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ export const resolve = Effect.fn("SessionTools.resolve")(function* (input: {
tools[item.id] = tool({
description: item.description,
inputSchema: jsonSchema(schema),
...(item.strict !== undefined && { strict: item.strict }),
execute(args, options) {
return run.promise(
Effect.gen(function* () {
Expand Down
3 changes: 3 additions & 0 deletions packages/opencode/src/tool/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ const layer = Layer.effect(
id,
parameters,
jsonSchema,
strict: def.strict,
description: def.description,
execute: (args, toolCtx) =>
Effect.gen(function* () {
Expand Down Expand Up @@ -309,6 +310,7 @@ const layer = Layer.effect(
description: tool.description,
parameters: tool.parameters,
jsonSchema: tool.jsonSchema,
strict: tool.strict,
}
yield* plugin.trigger("tool.definition", { toolID: tool.id }, output)
const jsonSchema =
Expand All @@ -326,6 +328,7 @@ const layer = Layer.effect(
.join("\n"),
parameters: output.parameters,
jsonSchema,
strict: output.strict,
execute: tool.execute,
formatValidationError: tool.formatValidationError,
}
Expand Down
1 change: 1 addition & 0 deletions packages/opencode/src/tool/tool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export interface Def<
description: string
parameters: Parameters
jsonSchema?: JSONSchema7
strict?: boolean
execute(args: Schema.Schema.Type<Parameters>, ctx: Context): Effect.Effect<ExecuteResult<M>>
formatValidationError?(error: unknown): string
}
Expand Down
61 changes: 61 additions & 0 deletions packages/opencode/test/provider/transform.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -586,6 +586,67 @@ describe("ProviderTransform.options - gpt-5 textVerbosity", () => {
expect(result.tools.lookup.strict).toBe(false)
})

test("a tool's strict flag survives on bedrock and is still overridden on mantle", async () => {
const prepare = (npm: string, apiId: string, url: string) =>
Effect.runPromise(
LLMRequestPrep.prepare({
user: {
id: "msg_user-test",
sessionID,
role: "user",
time: { created: Date.now() },
agent: "test",
model: { providerID: "amazon-bedrock", modelID: apiId },
} as any,
sessionID,
model: {
...createGpt5Model(apiId),
id: `amazon-bedrock/${apiId}`,
providerID: "amazon-bedrock",
api: { id: apiId, url, npm },
},
agent: {
name: "test",
mode: "primary",
options: {},
permission: [],
} as any,
system: [],
messages: [{ role: "user", content: "Hello" }],
tools: {
lookup: {
description: "Look up a value",
inputSchema: jsonSchema({ type: "object", properties: {} }),
strict: true,
},
},
provider: { id: "amazon-bedrock", options: {} } as any,
auth: undefined,
plugin: {
trigger: (_name: string, _input: unknown, output: unknown) => Effect.succeed(output),
list: () => Effect.succeed([]),
init: () => Effect.void,
} as any,
flags: { outputTokenMax: 32_000, client: "test" } as any,
isWorkflow: false,
}),
)

const converse = await prepare(
"@ai-sdk/amazon-bedrock",
"anthropic.claude-sonnet-4-6",
"https://bedrock-runtime.us-east-1.amazonaws.com",
)
const mantle = await prepare(
"@ai-sdk/amazon-bedrock/mantle",
"openai.gpt-5.5",
"https://bedrock-mantle.us-east-2.api.aws/openai/v1",
)

expect(converse.tools.lookup.strict).toBe(true)
expect(mantle.tools.lookup.strict).toBe(false)
})

test("gpt-5.1 should have textVerbosity set to low", () => {
const model = createGpt5Model("gpt-5.1")
const result = ProviderTransform.options({ model, sessionID, providerOptions: {} })
Expand Down
33 changes: 33 additions & 0 deletions packages/opencode/test/tool/registry.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,39 @@ describe("tool.registry", () => {
}),
)

it.instance("carries strict from a custom tool definition", () =>
Effect.gen(function* () {
const test = yield* TestInstance
const tool = path.join(test.directory, ".opencode", "tool")
yield* Effect.promise(() => fs.mkdir(tool, { recursive: true }))
yield* Effect.promise(() =>
Bun.write(
path.join(tool, "strict_tool.ts"),
[
"export default {",
" description: 'strict tool',",
" args: {},",
" strict: true,",
" execute: async () => 'ok',",
"}",
"",
].join("\n"),
),
)

const registry = yield* ToolRegistry.Service
const agents = yield* Agent.Service
const tools = yield* registry.tools({
providerID: ProviderV2.ID.opencode,
modelID: ModelV2.ID.make("test"),
agent: yield* agents.defaultInfo(),
})

expect(tools.find((item) => item.id === "strict_tool")?.strict).toBe(true)
expect(tools.filter((item) => item.id !== "strict_tool").every((item) => item.strict === undefined)).toBe(true)
}),
)

it.instance("ignores non-tool exports in .opencode/tool files", () =>
Effect.gen(function* () {
const test = yield* TestInstance
Expand Down
5 changes: 4 additions & 1 deletion packages/plugin/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -331,5 +331,8 @@ export interface Hooks {
/**
* Modify tool definitions (description and parameters) sent to LLM
*/
"tool.definition"?: (input: { toolID: string }, output: { description: string; parameters: any }) => Promise<void>
"tool.definition"?: (
input: { toolID: string },
output: { description: string; parameters: any; strict?: boolean },
) => Promise<void>
}
1 change: 1 addition & 0 deletions packages/plugin/src/tool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export type ToolResult =
export function tool<Args extends z.ZodRawShape>(input: {
description: string
args: Args
strict?: boolean
execute(args: z.infer<z.ZodObject<Args>>, context: ToolContext): Promise<ToolResult>
}) {
return input
Expand Down
Loading