mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-08 18:30:00 -04:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d6f36bb750 |
@@ -398,14 +398,12 @@ export const make = Effect.fn("PluginHost.make")(function* (plugin: PluginV2.Int
|
|||||||
input: event.input,
|
input: event.input,
|
||||||
result: event.result,
|
result: event.result,
|
||||||
output: event.output,
|
output: event.output,
|
||||||
outputPaths: event.outputPaths,
|
|
||||||
}
|
}
|
||||||
return Reflect.apply(callback, undefined, [output]).pipe(
|
return Reflect.apply(callback, undefined, [output]).pipe(
|
||||||
Effect.tap(() =>
|
Effect.tap(() =>
|
||||||
Effect.sync(() => {
|
Effect.sync(() => {
|
||||||
event.result = output.result
|
event.result = output.result
|
||||||
event.output = output.output
|
event.output = output.output
|
||||||
event.outputPaths = output.outputPaths
|
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -22,11 +22,6 @@ export interface BoundInput {
|
|||||||
readonly output: ToolOutput
|
readonly output: ToolOutput
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BoundResult {
|
|
||||||
readonly output: ToolOutput
|
|
||||||
readonly outputPaths: ReadonlyArray<string>
|
|
||||||
}
|
|
||||||
|
|
||||||
export class StorageError extends Schema.TaggedErrorClass<StorageError>()("ToolOutputStore.StorageError", {
|
export class StorageError extends Schema.TaggedErrorClass<StorageError>()("ToolOutputStore.StorageError", {
|
||||||
operation: Schema.Literals(["encode", "write"]),
|
operation: Schema.Literals(["encode", "write"]),
|
||||||
cause: Schema.Defect(),
|
cause: Schema.Defect(),
|
||||||
@@ -41,7 +36,7 @@ export type Error = StorageError
|
|||||||
|
|
||||||
export interface Interface {
|
export interface Interface {
|
||||||
readonly limits: () => Effect.Effect<{ readonly maxLines: number; readonly maxBytes: number }>
|
readonly limits: () => Effect.Effect<{ readonly maxLines: number; readonly maxBytes: number }>
|
||||||
readonly bound: (input: BoundInput) => Effect.Effect<BoundResult, Error>
|
readonly bound: (input: BoundInput) => Effect.Effect<ToolOutput, Error>
|
||||||
readonly cleanup: () => Effect.Effect<void>
|
readonly cleanup: () => Effect.Effect<void>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,26 +145,20 @@ const layer = Layer.effect(
|
|||||||
lineCount(contextual) <= outputLimits.maxLines &&
|
lineCount(contextual) <= outputLimits.maxLines &&
|
||||||
Buffer.byteLength(contextual, "utf-8") <= outputLimits.maxBytes
|
Buffer.byteLength(contextual, "utf-8") <= outputLimits.maxBytes
|
||||||
)
|
)
|
||||||
return {
|
return input.output
|
||||||
output: input.output,
|
|
||||||
outputPaths: [],
|
|
||||||
}
|
|
||||||
|
|
||||||
const outputPath = yield* write(contextual)
|
const outputPath = yield* write(contextual)
|
||||||
const marker = `... output truncated; full content saved to ${outputPath} ...`
|
const marker = `... output truncated; full content saved to ${outputPath} ...`
|
||||||
|
|
||||||
return {
|
return {
|
||||||
output: {
|
structured: input.output.structured,
|
||||||
structured: input.output.structured,
|
content: [
|
||||||
content: [
|
{
|
||||||
{
|
type: "text" as const,
|
||||||
type: "text" as const,
|
text: boundedPreview(contextual, marker, outputLimits.maxLines, outputLimits.maxBytes),
|
||||||
text: boundedPreview(contextual, marker, outputLimits.maxLines, outputLimits.maxBytes),
|
},
|
||||||
},
|
...media,
|
||||||
...media,
|
],
|
||||||
],
|
|
||||||
},
|
|
||||||
outputPaths: [outputPath],
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -55,4 +55,3 @@ Producer capture limits are separate. For example, Bash keeps `AppProcess.maxOut
|
|||||||
## Current Gaps
|
## Current Gaps
|
||||||
|
|
||||||
- MCP and future Session-scoped registrations still need an explicit canonical registration design.
|
- MCP and future Session-scoped registrations still need an explicit canonical registration design.
|
||||||
- The public Session result shape currently exposes managed `outputPaths`; full storage encapsulation requires a future opaque managed-output reference design.
|
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ export interface AfterEvent {
|
|||||||
readonly input: unknown
|
readonly input: unknown
|
||||||
result: ToolResultValue
|
result: ToolResultValue
|
||||||
output?: ToolOutput
|
output?: ToolOutput
|
||||||
outputPaths?: ReadonlyArray<string>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Interface {
|
export interface Interface {
|
||||||
|
|||||||
@@ -55,7 +55,6 @@ export interface Materialization {
|
|||||||
export interface Settlement {
|
export interface Settlement {
|
||||||
readonly result: ToolResultValue
|
readonly result: ToolResultValue
|
||||||
readonly output?: ToolOutput
|
readonly output?: ToolOutput
|
||||||
readonly outputPaths?: ReadonlyArray<string>
|
|
||||||
readonly error?: SessionError.Error
|
readonly error?: SessionError.Error
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,20 +156,13 @@ const registryLayer = Layer.effect(
|
|||||||
if ("result" in pending) {
|
if ("result" in pending) {
|
||||||
settlement = pending
|
settlement = pending
|
||||||
} else {
|
} else {
|
||||||
const bounded = yield* resources.bound({
|
const output = yield* resources.bound({
|
||||||
sessionID: input.sessionID,
|
sessionID: input.sessionID,
|
||||||
callID: input.call.id,
|
callID: input.call.id,
|
||||||
output: { structured: pending.output.structured, content: yield* normalizeImages(pending.output.content) },
|
output: { structured: pending.output.structured, content: yield* normalizeImages(pending.output.content) },
|
||||||
})
|
})
|
||||||
const result = ToolOutput.toResultValue(bounded.output)
|
const result = ToolOutput.toResultValue(output)
|
||||||
settlement =
|
settlement = result.type === "error" ? { result } : { result, output }
|
||||||
result.type === "error"
|
|
||||||
? bounded.outputPaths.length > 0
|
|
||||||
? { result, outputPaths: bounded.outputPaths }
|
|
||||||
: { result }
|
|
||||||
: bounded.outputPaths.length > 0
|
|
||||||
? { result, output: bounded.output, outputPaths: bounded.outputPaths }
|
|
||||||
: { result, output: bounded.output }
|
|
||||||
}
|
}
|
||||||
const afterEvent: ToolHooks.AfterEvent = {
|
const afterEvent: ToolHooks.AfterEvent = {
|
||||||
tool: input.call.name,
|
tool: input.call.name,
|
||||||
@@ -181,13 +173,11 @@ const registryLayer = Layer.effect(
|
|||||||
input: beforeEvent.input,
|
input: beforeEvent.input,
|
||||||
result: settlement.result,
|
result: settlement.result,
|
||||||
output: settlement.output,
|
output: settlement.output,
|
||||||
outputPaths: settlement.outputPaths,
|
|
||||||
}
|
}
|
||||||
yield* toolHooks.runAfter(afterEvent)
|
yield* toolHooks.runAfter(afterEvent)
|
||||||
return {
|
return {
|
||||||
result: afterEvent.result,
|
result: afterEvent.result,
|
||||||
...(afterEvent.output !== undefined ? { output: afterEvent.output } : {}),
|
...(afterEvent.output !== undefined ? { output: afterEvent.output } : {}),
|
||||||
...(afterEvent.outputPaths !== undefined ? { outputPaths: afterEvent.outputPaths } : {}),
|
|
||||||
...(settlement.error !== undefined ? { error: settlement.error } : {}),
|
...(settlement.error !== undefined ? { error: settlement.error } : {}),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -20,11 +20,8 @@ const outputStore = Layer.mock(ToolOutputStore.Service, {
|
|||||||
return Effect.sync(() => bounds.push(input)).pipe(
|
return Effect.sync(() => bounds.push(input)).pipe(
|
||||||
Effect.as(
|
Effect.as(
|
||||||
input.callID === "call-bounded"
|
input.callID === "call-bounded"
|
||||||
? {
|
? { structured: {}, content: [{ type: "text" as const, text: "bounded reference" }] }
|
||||||
output: { structured: {}, content: [{ type: "text" as const, text: "bounded reference" }] },
|
: input.output,
|
||||||
outputPaths: ["/managed/generic"],
|
|
||||||
}
|
|
||||||
: { output: input.output, outputPaths: [] },
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@@ -282,7 +279,6 @@ describe("ToolRegistry", () => {
|
|||||||
).toEqual({
|
).toEqual({
|
||||||
result: { type: "text", value: "bounded reference" },
|
result: { type: "text", value: "bounded reference" },
|
||||||
output: { structured: {}, content: [{ type: "text", text: "bounded reference" }] },
|
output: { structured: {}, content: [{ type: "text", text: "bounded reference" }] },
|
||||||
outputPaths: ["/managed/generic"],
|
|
||||||
})
|
})
|
||||||
expect(bounds).toHaveLength(1)
|
expect(bounds).toHaveLength(1)
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -9,11 +9,19 @@ import { Config } from "@opencode-ai/core/config"
|
|||||||
import { ConfigToolOutput } from "@opencode-ai/core/config/tool-output"
|
import { ConfigToolOutput } from "@opencode-ai/core/config/tool-output"
|
||||||
import { SessionV2 } from "@opencode-ai/core/session"
|
import { SessionV2 } from "@opencode-ai/core/session"
|
||||||
import { ToolOutputStore } from "@opencode-ai/core/tool-output-store"
|
import { ToolOutputStore } from "@opencode-ai/core/tool-output-store"
|
||||||
|
import type { ToolOutput } from "@opencode-ai/ai"
|
||||||
import { testEffect } from "./lib/effect"
|
import { testEffect } from "./lib/effect"
|
||||||
import { tmpdir } from "./fixture/tmpdir"
|
import { tmpdir } from "./fixture/tmpdir"
|
||||||
|
|
||||||
const sessionID = SessionV2.ID.make("ses_tool_output_store")
|
const sessionID = SessionV2.ID.make("ses_tool_output_store")
|
||||||
|
|
||||||
|
const retainedPath = (output: ToolOutput) => {
|
||||||
|
const text = output.content.find((item) => item.type === "text")?.text
|
||||||
|
const match = text && /full content saved to (.+) \.\.\./.exec(text)
|
||||||
|
if (!match) throw new Error("expected managed output path in bounded content")
|
||||||
|
return match[1]
|
||||||
|
}
|
||||||
|
|
||||||
const withStore = <A, E, R>(
|
const withStore = <A, E, R>(
|
||||||
body: (input: { root: string; store: ToolOutputStore.Interface; fs: FSUtil.Interface }) => Effect.Effect<A, E, R>,
|
body: (input: { root: string; store: ToolOutputStore.Interface; fs: FSUtil.Interface }) => Effect.Effect<A, E, R>,
|
||||||
config?: Config.Info,
|
config?: Config.Info,
|
||||||
@@ -61,11 +69,10 @@ describe("ToolOutputStore", () => {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
expect(result.output.structured).toEqual({ kind: "report" })
|
expect(result.structured).toEqual({ kind: "report" })
|
||||||
expect(result.outputPaths).toHaveLength(1)
|
expect(yield* fs.readFileString(retainedPath(result))).toBe(first + second)
|
||||||
expect(yield* fs.readFileString(result.outputPaths[0])).toBe(first + second)
|
if (result.content[0]?.type !== "text") throw new Error("expected text preview")
|
||||||
if (result.output.content[0]?.type !== "text") throw new Error("expected text preview")
|
expect(Buffer.byteLength(result.content[0].text)).toBeLessThanOrEqual(ToolOutputStore.MAX_BYTES)
|
||||||
expect(Buffer.byteLength(result.output.content[0].text)).toBeLessThanOrEqual(ToolOutputStore.MAX_BYTES)
|
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -75,10 +82,9 @@ describe("ToolOutputStore", () => {
|
|||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const structured = { text: "x".repeat(ToolOutputStore.MAX_BYTES) }
|
const structured = { text: "x".repeat(ToolOutputStore.MAX_BYTES) }
|
||||||
const result = yield* store.bound({ sessionID, callID: "call-json", output: { structured, content: [] } })
|
const result = yield* store.bound({ sessionID, callID: "call-json", output: { structured, content: [] } })
|
||||||
expect(result.output.structured).toEqual(structured)
|
expect(result.structured).toEqual(structured)
|
||||||
expect(result.outputPaths).toHaveLength(1)
|
expect(JSON.parse(yield* fs.readFileString(retainedPath(result)))).toEqual(structured)
|
||||||
expect(JSON.parse(yield* fs.readFileString(result.outputPaths[0]))).toEqual(structured)
|
expect(result.content).toHaveLength(1)
|
||||||
expect(result.output.content).toHaveLength(1)
|
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -95,10 +101,9 @@ describe("ToolOutputStore", () => {
|
|||||||
content: [{ type: "file", uri: `data:image/png;base64,${data}`, mime: "image/png", name: "pixel.png" }],
|
content: [{ type: "file", uri: `data:image/png;base64,${data}`, mime: "image/png", name: "pixel.png" }],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
expect(result.outputPaths).toEqual([])
|
expect(result.structured).toEqual({ caption: "pixel" })
|
||||||
expect(result.output.structured).toEqual({ caption: "pixel" })
|
expect(result.content).toHaveLength(1)
|
||||||
expect(result.output.content).toHaveLength(1)
|
expect(result.content[0]).toEqual({
|
||||||
expect(result.output.content[0]).toEqual({
|
|
||||||
type: "file",
|
type: "file",
|
||||||
uri: `data:image/png;base64,${data}`,
|
uri: `data:image/png;base64,${data}`,
|
||||||
mime: "image/png",
|
mime: "image/png",
|
||||||
@@ -124,9 +129,9 @@ describe("ToolOutputStore", () => {
|
|||||||
output: { structured: { caption: "pixel" }, content: [{ type: "text", text }, media] },
|
output: { structured: { caption: "pixel" }, content: [{ type: "text", text }, media] },
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(result.output.structured).toEqual({ caption: "pixel" })
|
expect(result.structured).toEqual({ caption: "pixel" })
|
||||||
expect(result.output.content[1]).toEqual(media)
|
expect(result.content[1]).toEqual(media)
|
||||||
expect(yield* fs.readFileString(result.outputPaths[0])).toBe(text)
|
expect(yield* fs.readFileString(retainedPath(result))).toBe(text)
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -136,10 +141,7 @@ describe("ToolOutputStore", () => {
|
|||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const text = "x".repeat(30_000)
|
const text = "x".repeat(30_000)
|
||||||
const output = { structured: { output: text }, content: [{ type: "text" as const, text }] }
|
const output = { structured: { output: text }, content: [{ type: "text" as const, text }] }
|
||||||
expect(yield* store.bound({ sessionID, callID: "call-duplicated", output })).toEqual({
|
expect(yield* store.bound({ sessionID, callID: "call-duplicated", output })).toEqual(output)
|
||||||
output,
|
|
||||||
outputPaths: [],
|
|
||||||
})
|
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -166,10 +168,7 @@ describe("ToolOutputStore", () => {
|
|||||||
withStore(({ store }) =>
|
withStore(({ store }) =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const output = { structured: { value: 1n }, content: [{ type: "text" as const, text: "readable text" }] }
|
const output = { structured: { value: 1n }, content: [{ type: "text" as const, text: "readable text" }] }
|
||||||
expect(yield* store.bound({ sessionID, callID: "call-unencodable", output })).toEqual({
|
expect(yield* store.bound({ sessionID, callID: "call-unencodable", output })).toEqual(output)
|
||||||
output,
|
|
||||||
outputPaths: [],
|
|
||||||
})
|
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -219,7 +218,7 @@ describe("ToolOutputStore", () => {
|
|||||||
callID: "call-config",
|
callID: "call-config",
|
||||||
output: { structured: {}, content: [{ type: "text", text: "one\ntwo\nthree" }] },
|
output: { structured: {}, content: [{ type: "text", text: "one\ntwo\nthree" }] },
|
||||||
})
|
})
|
||||||
expect(result.outputPaths).toHaveLength(1)
|
expect(retainedPath(result)).toContain("tool-output")
|
||||||
}),
|
}),
|
||||||
new Config.Info({ tool_output: new ConfigToolOutput.Info({ max_lines: 2, max_bytes: 1_000 }) }),
|
new Config.Info({ tool_output: new ConfigToolOutput.Info({ max_lines: 2, max_bytes: 1_000 }) }),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -325,7 +325,6 @@ describe("ReadTool", () => {
|
|||||||
call: { type: "tool-call", id: "call-large-image", name: "read", input: { path: "large.png" } },
|
call: { type: "tool-call", id: "call-large-image", name: "read", input: { path: "large.png" } },
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(settled.outputPaths).toBeUndefined()
|
|
||||||
expect(settled.output?.structured).toMatchObject({
|
expect(settled.output?.structured).toMatchObject({
|
||||||
uri: "file:///large.png",
|
uri: "file:///large.png",
|
||||||
name: "large.png",
|
name: "large.png",
|
||||||
|
|||||||
Vendored
+1
-1
@@ -248,7 +248,7 @@ mutable fields:
|
|||||||
| `ctx.aisdk.hook("language", callback)` | `language`, after inspecting `model`, `sdk`, and `options` |
|
| `ctx.aisdk.hook("language", callback)` | `language`, after inspecting `model`, `sdk`, and `options` |
|
||||||
| `ctx.session.hook("request", callback)` | `system`, `messages`, and the `tools` record immediately before model dispatch |
|
| `ctx.session.hook("request", callback)` | `system`, `messages`, and the `tools` record immediately before model dispatch |
|
||||||
| `ctx.tool.hook("execute.before", callback)` | `input`, before the selected tool executes |
|
| `ctx.tool.hook("execute.before", callback)` | `input`, before the selected tool executes |
|
||||||
| `ctx.tool.hook("execute.after", callback)` | `result`, `output`, and `outputPaths`, after execution settles |
|
| `ctx.tool.hook("execute.after", callback)` | `result` and `output`, after execution settles |
|
||||||
|
|
||||||
For example, remove a tool from selected model requests and normalize another
|
For example, remove a tool from selected model requests and normalize another
|
||||||
tool's input:
|
tool's input:
|
||||||
|
|||||||
@@ -290,7 +290,6 @@ export interface ToolExecuteAfterEvent {
|
|||||||
readonly input: unknown
|
readonly input: unknown
|
||||||
result: ToolResultValue
|
result: ToolResultValue
|
||||||
output?: ToolOutput
|
output?: ToolOutput
|
||||||
outputPaths?: ReadonlyArray<string>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RegisterOptions {
|
export interface RegisterOptions {
|
||||||
|
|||||||
@@ -18577,12 +18577,6 @@
|
|||||||
"$ref": "#/components/schemas/LLMToolContent"
|
"$ref": "#/components/schemas/LLMToolContent"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"outputPaths": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"result": {},
|
"result": {},
|
||||||
"provider": {
|
"provider": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
@@ -26400,12 +26394,6 @@
|
|||||||
"$ref": "#/components/schemas/LLMToolContent"
|
"$ref": "#/components/schemas/LLMToolContent"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"outputPaths": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"result": {},
|
"result": {},
|
||||||
"provider": {
|
"provider": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
@@ -27526,12 +27514,6 @@
|
|||||||
"$ref": "#/components/schemas/LLMToolContent"
|
"$ref": "#/components/schemas/LLMToolContent"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"outputPaths": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"structured": {
|
"structured": {
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
@@ -29006,12 +28988,6 @@
|
|||||||
"$ref": "#/components/schemas/LLMToolContent"
|
"$ref": "#/components/schemas/LLMToolContent"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"outputPaths": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"result": {},
|
"result": {},
|
||||||
"provider": {
|
"provider": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
@@ -34876,12 +34852,6 @@
|
|||||||
"$ref": "#/components/schemas/LLMToolContent"
|
"$ref": "#/components/schemas/LLMToolContent"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"outputPaths": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"result": {},
|
"result": {},
|
||||||
"provider": {
|
"provider": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
|||||||
+1
-1
@@ -248,7 +248,7 @@ mutable fields:
|
|||||||
| `ctx.aisdk.hook("language", callback)` | `language`, after inspecting `model`, `sdk`, and `options` |
|
| `ctx.aisdk.hook("language", callback)` | `language`, after inspecting `model`, `sdk`, and `options` |
|
||||||
| `ctx.session.hook("request", callback)` | `system`, `messages`, and the `tools` record immediately before model dispatch |
|
| `ctx.session.hook("request", callback)` | `system`, `messages`, and the `tools` record immediately before model dispatch |
|
||||||
| `ctx.tool.hook("execute.before", callback)` | `input`, before the selected tool executes |
|
| `ctx.tool.hook("execute.before", callback)` | `input`, before the selected tool executes |
|
||||||
| `ctx.tool.hook("execute.after", callback)` | `result`, `output`, and `outputPaths`, after execution settles |
|
| `ctx.tool.hook("execute.after", callback)` | `result` and `output`, after execution settles |
|
||||||
|
|
||||||
For example, remove a tool from selected model requests and normalize another
|
For example, remove a tool from selected model requests and normalize another
|
||||||
tool's input:
|
tool's input:
|
||||||
|
|||||||
@@ -312,7 +312,6 @@ Compatibility:
|
|||||||
|
|
||||||
Affected schema:
|
Affected schema:
|
||||||
|
|
||||||
- New optional managed `outputPath` and `outputPaths` fields on tool results and completed Session tool state.
|
|
||||||
- Absolute managed output paths accepted by ordinary `read` and `grep` inputs.
|
- Absolute managed output paths accepted by ordinary `read` and `grep` inputs.
|
||||||
|
|
||||||
Change:
|
Change:
|
||||||
@@ -327,7 +326,7 @@ Reason:
|
|||||||
|
|
||||||
Compatibility:
|
Compatibility:
|
||||||
|
|
||||||
- Managed output is retained for a bounded period and exposed as a normal host filesystem path.
|
- Managed output is retained for a bounded period and exposed in bounded tool content as a normal host filesystem path.
|
||||||
|
|
||||||
### Location-Scoped Filesystem Read And Search Contracts
|
### Location-Scoped Filesystem Read And Search Contracts
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user