fix(core): honor configured agent step limits (#33142)
This commit is contained in:
parent
503309d244
commit
4f1a9d7aef
5 changed files with 76 additions and 83 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
export * as SessionRunner from "./index"
|
export * as SessionRunner from "./index"
|
||||||
|
|
||||||
import type { LLMError } from "@opencode-ai/llm"
|
import type { LLMError } from "@opencode-ai/llm"
|
||||||
import { Context, Effect, Schema } from "effect"
|
import { Context, Effect } from "effect"
|
||||||
import { SessionSchema } from "../schema"
|
import { SessionSchema } from "../schema"
|
||||||
import type { ContextSnapshotDecodeError, MessageDecodeError } from "../error"
|
import type { ContextSnapshotDecodeError, MessageDecodeError } from "../error"
|
||||||
import { SessionRunnerModel } from "./model"
|
import { SessionRunnerModel } from "./model"
|
||||||
|
|
@ -9,20 +9,11 @@ import type { SystemContext } from "../../system-context/index"
|
||||||
import type { SessionContextEpoch } from "../context-epoch"
|
import type { SessionContextEpoch } from "../context-epoch"
|
||||||
import type { ToolOutputStore } from "../../tool-output-store"
|
import type { ToolOutputStore } from "../../tool-output-store"
|
||||||
|
|
||||||
export class StepLimitExceededError extends Schema.TaggedErrorClass<StepLimitExceededError>()(
|
|
||||||
"SessionRunner.StepLimitExceededError",
|
|
||||||
{
|
|
||||||
sessionID: SessionSchema.ID,
|
|
||||||
limit: Schema.Int,
|
|
||||||
},
|
|
||||||
) {}
|
|
||||||
|
|
||||||
export type RunError =
|
export type RunError =
|
||||||
| LLMError
|
| LLMError
|
||||||
| SessionRunnerModel.Error
|
| SessionRunnerModel.Error
|
||||||
| MessageDecodeError
|
| MessageDecodeError
|
||||||
| ContextSnapshotDecodeError
|
| ContextSnapshotDecodeError
|
||||||
| StepLimitExceededError
|
|
||||||
| SystemContext.InitializationBlocked
|
| SystemContext.InitializationBlocked
|
||||||
| SessionContextEpoch.AgentReplacementBlocked
|
| SessionContextEpoch.AgentReplacementBlocked
|
||||||
| ToolOutputStore.Error
|
| ToolOutputStore.Error
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import {
|
||||||
LLMClient,
|
LLMClient,
|
||||||
LLMError,
|
LLMError,
|
||||||
LLMEvent,
|
LLMEvent,
|
||||||
|
Message,
|
||||||
SystemPart,
|
SystemPart,
|
||||||
isContextOverflowFailure,
|
isContextOverflowFailure,
|
||||||
type ProviderErrorEvent,
|
type ProviderErrorEvent,
|
||||||
|
|
@ -29,10 +30,11 @@ import { SessionHistory } from "../history"
|
||||||
import { SessionInput } from "../input"
|
import { SessionInput } from "../input"
|
||||||
import { SessionSchema } from "../schema"
|
import { SessionSchema } from "../schema"
|
||||||
import { SessionStore } from "../store"
|
import { SessionStore } from "../store"
|
||||||
import { type RunError, Service, StepLimitExceededError } from "./index"
|
import { type RunError, Service } from "./index"
|
||||||
import { SessionRunnerModel } from "./model"
|
import { SessionRunnerModel } from "./model"
|
||||||
import { createLLMEventPublisher } from "./publish-llm-event"
|
import { createLLMEventPublisher } from "./publish-llm-event"
|
||||||
import { toLLMMessages } from "./to-llm-message"
|
import { toLLMMessages } from "./to-llm-message"
|
||||||
|
import { MAX_STEPS_PROMPT } from "./max-steps"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Runs one durable coding-agent Session until it settles.
|
* Runs one durable coding-agent Session until it settles.
|
||||||
|
|
@ -45,7 +47,7 @@ import { toLLMMessages } from "./to-llm-message"
|
||||||
* - [ ] Replace local ownership with durable multi-node ownership when clustered.
|
* - [ ] Replace local ownership with durable multi-node ownership when clustered.
|
||||||
* - [ ] Mark busy, retrying, idle, interrupted, or terminal-failure status durably.
|
* - [ ] Mark busy, retrying, idle, interrupted, or terminal-failure status durably.
|
||||||
* - [ ] Honor interruption and reject stale work after runtime attachment replacement.
|
* - [ ] Honor interruption and reject stale work after runtime attachment replacement.
|
||||||
* - [x] Bound model steps.
|
* - [x] Honor optional agent step limits.
|
||||||
* - [ ] Bound provider retries and repeated identical tool calls.
|
* - [ ] Bound provider retries and repeated identical tool calls.
|
||||||
*
|
*
|
||||||
* - Runtime context assembly
|
* - Runtime context assembly
|
||||||
|
|
@ -80,13 +82,10 @@ import { toLLMMessages } from "./to-llm-message"
|
||||||
* Durable activity recovery remains a separate future slice with an explicit retry policy.
|
* Durable activity recovery remains a separate future slice with an explicit retry policy.
|
||||||
*
|
*
|
||||||
* The current slice loads V2 history, translates it, resolves a model through a core service, and persists one
|
* The current slice loads V2 history, translates it, resolves a model through a core service, and persists one
|
||||||
* provider turn. Registry definitions are advertised, local tool calls are settled durably, and a
|
* provider turn. Registry definitions are advertised, local tool calls are settled durably, and an
|
||||||
* bounded explicit loop starts the next provider turn after local settlement.
|
* explicit loop starts the next provider turn after local settlement. Configured agent step limits bound the loop.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// QUESTION: Did this exist previously, or did we add this limit? Does it make sense?
|
|
||||||
const MAX_STEPS = 25
|
|
||||||
|
|
||||||
export const layer = Layer.effect(
|
export const layer = Layer.effect(
|
||||||
Service,
|
Service,
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
|
|
@ -175,6 +174,7 @@ export const layer = Layer.effect(
|
||||||
const runTurnAttempt = Effect.fn("SessionRunner.runTurn")(function* (
|
const runTurnAttempt = Effect.fn("SessionRunner.runTurn")(function* (
|
||||||
sessionID: SessionSchema.ID,
|
sessionID: SessionSchema.ID,
|
||||||
promotion: SessionInput.Delivery | undefined,
|
promotion: SessionInput.Delivery | undefined,
|
||||||
|
step: number,
|
||||||
recoverOverflow?: typeof compaction.compactAfterOverflow,
|
recoverOverflow?: typeof compaction.compactAfterOverflow,
|
||||||
) {
|
) {
|
||||||
const session = yield* getSession(sessionID)
|
const session = yield* getSession(sessionID)
|
||||||
|
|
@ -214,7 +214,8 @@ export const layer = Layer.effect(
|
||||||
const model = yield* models.resolve(session)
|
const model = yield* models.resolve(session)
|
||||||
const entries = yield* SessionHistory.entriesForRunner(db, session.id, system.baselineSeq)
|
const entries = yield* SessionHistory.entriesForRunner(db, session.id, system.baselineSeq)
|
||||||
const context = entries.map((entry) => entry.message)
|
const context = entries.map((entry) => entry.message)
|
||||||
const toolMaterialization = yield* tools.materialize(agent.info?.permissions)
|
const isLastStep = agent.info?.steps !== undefined && step >= agent.info.steps
|
||||||
|
const toolMaterialization = isLastStep ? undefined : yield* tools.materialize(agent.info?.permissions)
|
||||||
const promptCacheKey = /^ses_[0-9a-f]{64}$/.test(session.id) ? session.id.slice(4) : session.id
|
const promptCacheKey = /^ses_[0-9a-f]{64}$/.test(session.id) ? session.id.slice(4) : session.id
|
||||||
const request = LLM.request({
|
const request = LLM.request({
|
||||||
model,
|
model,
|
||||||
|
|
@ -222,8 +223,9 @@ export const layer = Layer.effect(
|
||||||
system: [agent.info?.system, system.baseline]
|
system: [agent.info?.system, system.baseline]
|
||||||
.filter((part): part is string => part !== undefined && part.length > 0)
|
.filter((part): part is string => part !== undefined && part.length > 0)
|
||||||
.map(SystemPart.make),
|
.map(SystemPart.make),
|
||||||
messages: toLLMMessages(context, model),
|
messages: [...toLLMMessages(context, model), ...(isLastStep ? [Message.assistant(MAX_STEPS_PROMPT)] : [])],
|
||||||
tools: toolMaterialization.definitions,
|
tools: toolMaterialization?.definitions ?? [],
|
||||||
|
toolChoice: isLastStep ? "none" : undefined,
|
||||||
})
|
})
|
||||||
if (yield* compaction.compactIfNeeded({ sessionID: session.id, entries, model, request }))
|
if (yield* compaction.compactIfNeeded({ sessionID: session.id, entries, model, request }))
|
||||||
return yield* Effect.die(rebuildPreparedTurn())
|
return yield* Effect.die(rebuildPreparedTurn())
|
||||||
|
|
@ -254,6 +256,10 @@ export const layer = Layer.effect(
|
||||||
}
|
}
|
||||||
yield* publish(event)
|
yield* publish(event)
|
||||||
if (event.type !== "tool-call" || event.providerExecuted) return
|
if (event.type !== "tool-call" || event.providerExecuted) return
|
||||||
|
if (!toolMaterialization) {
|
||||||
|
yield* withPublication(publisher.failUnsettledTools("Tools are disabled after the maximum agent steps"))
|
||||||
|
return
|
||||||
|
}
|
||||||
needsContinuation = true
|
needsContinuation = true
|
||||||
const assistantMessageID = yield* publisher.assistantMessageID(event.id)
|
const assistantMessageID = yield* publisher.assistantMessageID(event.id)
|
||||||
yield* Effect.uninterruptibleMask((restore) =>
|
yield* Effect.uninterruptibleMask((restore) =>
|
||||||
|
|
@ -340,31 +346,32 @@ export const layer = Layer.effect(
|
||||||
type RunTurn = (
|
type RunTurn = (
|
||||||
sessionID: SessionSchema.ID,
|
sessionID: SessionSchema.ID,
|
||||||
promotion: SessionInput.Delivery | undefined,
|
promotion: SessionInput.Delivery | undefined,
|
||||||
|
step: number,
|
||||||
) => Effect.Effect<boolean, RunError>
|
) => Effect.Effect<boolean, RunError>
|
||||||
|
|
||||||
const runAfterOverflowCompaction: RunTurn = Effect.fnUntraced(function* (sessionID, promotion) {
|
const runAfterOverflowCompaction: RunTurn = Effect.fnUntraced(function* (sessionID, promotion, step) {
|
||||||
return yield* runTurnAttempt(sessionID, promotion).pipe(
|
return yield* runTurnAttempt(sessionID, promotion, step).pipe(
|
||||||
Effect.catchDefect(
|
Effect.catchDefect(
|
||||||
Effect.fnUntraced(function* (defect) {
|
Effect.fnUntraced(function* (defect) {
|
||||||
if (!(defect instanceof TurnTransitionError)) return yield* Effect.die(defect)
|
if (!(defect instanceof TurnTransitionError)) return yield* Effect.die(defect)
|
||||||
if (defect.transition._tag === "ContinueAfterOverflowCompaction")
|
if (defect.transition._tag === "ContinueAfterOverflowCompaction")
|
||||||
return yield* Effect.die("Post-compaction provider attempt cannot recover another overflow")
|
return yield* Effect.die("Post-compaction provider attempt cannot recover another overflow")
|
||||||
yield* Effect.yieldNow
|
yield* Effect.yieldNow
|
||||||
return yield* runAfterOverflowCompaction(sessionID, defect.transition.promotion)
|
return yield* runAfterOverflowCompaction(sessionID, defect.transition.promotion, step)
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
const runTurn: RunTurn = Effect.fnUntraced(function* (sessionID, promotion) {
|
const runTurn: RunTurn = Effect.fnUntraced(function* (sessionID, promotion, step) {
|
||||||
return yield* runTurnAttempt(sessionID, promotion, compaction.compactAfterOverflow).pipe(
|
return yield* runTurnAttempt(sessionID, promotion, step, compaction.compactAfterOverflow).pipe(
|
||||||
Effect.catchDefect(
|
Effect.catchDefect(
|
||||||
Effect.fnUntraced(function* (defect) {
|
Effect.fnUntraced(function* (defect) {
|
||||||
if (!(defect instanceof TurnTransitionError)) return yield* Effect.die(defect)
|
if (!(defect instanceof TurnTransitionError)) return yield* Effect.die(defect)
|
||||||
yield* Effect.yieldNow
|
yield* Effect.yieldNow
|
||||||
if (defect.transition._tag === "ContinueAfterOverflowCompaction")
|
if (defect.transition._tag === "ContinueAfterOverflowCompaction")
|
||||||
return yield* runAfterOverflowCompaction(sessionID, undefined)
|
return yield* runAfterOverflowCompaction(sessionID, undefined, step)
|
||||||
return yield* runTurn(sessionID, defect.transition.promotion)
|
return yield* runTurn(sessionID, defect.transition.promotion, step)
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
@ -382,14 +389,11 @@ export const layer = Layer.effect(
|
||||||
let openActivity = input.force === true || hasSteer || hasQueue
|
let openActivity = input.force === true || hasSteer || hasQueue
|
||||||
while (openActivity) {
|
while (openActivity) {
|
||||||
let needsContinuation = true
|
let needsContinuation = true
|
||||||
for (let step = 0; step < MAX_STEPS; step++) {
|
for (let step = 1; needsContinuation; step++) {
|
||||||
needsContinuation = yield* runTurn(input.sessionID, promotion)
|
needsContinuation = yield* runTurn(input.sessionID, promotion, step)
|
||||||
promotion = "steer"
|
promotion = "steer"
|
||||||
if (!needsContinuation) needsContinuation = yield* SessionInput.hasPending(db, input.sessionID, "steer")
|
if (!needsContinuation) needsContinuation = yield* SessionInput.hasPending(db, input.sessionID, "steer")
|
||||||
if (!needsContinuation) break
|
|
||||||
}
|
}
|
||||||
if (needsContinuation)
|
|
||||||
return yield* new StepLimitExceededError({ sessionID: input.sessionID, limit: MAX_STEPS })
|
|
||||||
openActivity = yield* SessionInput.hasPending(db, input.sessionID, "queue")
|
openActivity = yield* SessionInput.hasPending(db, input.sessionID, "queue")
|
||||||
promotion = openActivity ? "queue" : undefined
|
promotion = openActivity ? "queue" : undefined
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
CRITICAL - MAXIMUM STEPS REACHED
|
export const MAX_STEPS_PROMPT = `CRITICAL - MAXIMUM STEPS REACHED
|
||||||
|
|
||||||
The maximum number of steps allowed for this task has been reached. Tools are disabled until next user input. Respond with text only.
|
The maximum number of steps allowed for this task has been reached. Tools are disabled until next user input. Respond with text only.
|
||||||
|
|
||||||
|
|
@ -13,4 +13,4 @@ Response must include:
|
||||||
- List of any remaining tasks that were not completed
|
- List of any remaining tasks that were not completed
|
||||||
- Recommendations for what should be done next
|
- Recommendations for what should be done next
|
||||||
|
|
||||||
Any attempt to use tools is a critical violation. Respond with text ONLY.
|
Any attempt to use tools is a critical violation. Respond with text ONLY.`
|
||||||
|
|
@ -1461,6 +1461,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
requests.length = 0
|
requests.length = 0
|
||||||
|
executions.length = 0
|
||||||
responses = [
|
responses = [
|
||||||
fragmentFixture("text", "text-summary-2", ["## Goal\n- Preserve the updated task"]).completeEvents,
|
fragmentFixture("text", "text-summary-2", ["## Goal\n- Preserve the updated task"]).completeEvents,
|
||||||
fragmentFixture("text", "text-final-2", ["Continued again"]).completeEvents,
|
fragmentFixture("text", "text-final-2", ["Continued again"]).completeEvents,
|
||||||
|
|
@ -3177,7 +3178,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
it.effect("fails after the bounded number of local tool continuation steps", () =>
|
it.effect("continues past 25 local tool steps when the agent has no step limit", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
yield* setup
|
yield* setup
|
||||||
const session = yield* SessionV2.Service
|
const session = yield* SessionV2.Service
|
||||||
|
|
@ -3188,62 +3189,10 @@ describe("SessionRunnerLLM", () => {
|
||||||
executions.length = 0
|
executions.length = 0
|
||||||
streamGate = undefined
|
streamGate = undefined
|
||||||
streamStarted = undefined
|
streamStarted = undefined
|
||||||
responses = Array.from({ length: 25 }, (_, index) => [
|
|
||||||
LLMEvent.stepStart({ index: 0 }),
|
|
||||||
LLMEvent.toolCall({ id: `call-echo-${index}`, name: "echo", input: { text: `${index}` } }),
|
|
||||||
LLMEvent.stepFinish({ index: 0, reason: "tool-calls" }),
|
|
||||||
LLMEvent.finish({ reason: "tool-calls" }),
|
|
||||||
])
|
|
||||||
|
|
||||||
const failure = yield* session.resume(sessionID).pipe(Effect.flip)
|
|
||||||
|
|
||||||
expect(failure).toMatchObject({ _tag: "SessionRunner.StepLimitExceededError", sessionID, limit: 25 })
|
|
||||||
expect(requests).toHaveLength(25)
|
|
||||||
expect(executions).toHaveLength(25)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
it.effect("does not restart a capped tool loop for a coalesced stale wake", () =>
|
|
||||||
Effect.gen(function* () {
|
|
||||||
yield* setup
|
|
||||||
const session = yield* SessionV2.Service
|
|
||||||
const coordinator = yield* SessionRunCoordinator.Service
|
|
||||||
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Loop forever" }), resume: false })
|
|
||||||
|
|
||||||
requests.length = 0
|
|
||||||
responses = Array.from({ length: 25 }, (_, index) => [
|
|
||||||
LLMEvent.stepStart({ index: 0 }),
|
|
||||||
LLMEvent.toolCall({ id: `call-capped-${index}`, name: "echo", input: { text: `${index}` } }),
|
|
||||||
LLMEvent.stepFinish({ index: 0, reason: "tool-calls" }),
|
|
||||||
LLMEvent.finish({ reason: "tool-calls" }),
|
|
||||||
])
|
|
||||||
streamGate = yield* Deferred.make<void>()
|
|
||||||
streamStarted = yield* Deferred.make<void>()
|
|
||||||
|
|
||||||
const run = yield* session.resume(sessionID).pipe(Effect.forkChild)
|
|
||||||
yield* Deferred.await(streamStarted)
|
|
||||||
yield* coordinator.wake(sessionID)
|
|
||||||
yield* Deferred.succeed(streamGate, undefined)
|
|
||||||
expect(yield* Fiber.join(run).pipe(Effect.flip)).toMatchObject({ _tag: "SessionRunner.StepLimitExceededError" })
|
|
||||||
streamGate = undefined
|
|
||||||
streamStarted = undefined
|
|
||||||
yield* Effect.yieldNow
|
|
||||||
|
|
||||||
expect(requests).toHaveLength(25)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
it.effect("accepts a terminal response on the final bounded provider turn", () =>
|
|
||||||
Effect.gen(function* () {
|
|
||||||
yield* setup
|
|
||||||
const session = yield* SessionV2.Service
|
|
||||||
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Finish at the limit" }), resume: false })
|
|
||||||
|
|
||||||
requests.length = 0
|
|
||||||
responses = [
|
responses = [
|
||||||
...Array.from({ length: 24 }, (_, index) => [
|
...Array.from({ length: 25 }, (_, index) => [
|
||||||
LLMEvent.stepStart({ index: 0 }),
|
LLMEvent.stepStart({ index: 0 }),
|
||||||
LLMEvent.toolCall({ id: `call-terminal-${index}`, name: "echo", input: { text: `${index}` } }),
|
LLMEvent.toolCall({ id: `call-echo-${index}`, name: "echo", input: { text: `${index}` } }),
|
||||||
LLMEvent.stepFinish({ index: 0, reason: "tool-calls" }),
|
LLMEvent.stepFinish({ index: 0, reason: "tool-calls" }),
|
||||||
LLMEvent.finish({ reason: "tool-calls" }),
|
LLMEvent.finish({ reason: "tool-calls" }),
|
||||||
]),
|
]),
|
||||||
|
|
@ -3256,7 +3205,56 @@ describe("SessionRunnerLLM", () => {
|
||||||
|
|
||||||
yield* session.resume(sessionID)
|
yield* session.resume(sessionID)
|
||||||
|
|
||||||
expect(requests).toHaveLength(25)
|
expect(requests).toHaveLength(26)
|
||||||
|
expect(executions).toHaveLength(25)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
it.effect("forces a text response on an agent's configured final step", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
yield* setup
|
||||||
|
const agents = yield* AgentV2.Service
|
||||||
|
yield* agents.update((editor) =>
|
||||||
|
editor.update(AgentV2.ID.make("build"), (agent) => {
|
||||||
|
agent.steps = 2
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
const session = yield* SessionV2.Service
|
||||||
|
yield* session.prompt({ sessionID, prompt: new Prompt({ text: "Finish at the limit" }), resume: false })
|
||||||
|
|
||||||
|
requests.length = 0
|
||||||
|
executions.length = 0
|
||||||
|
responses = [
|
||||||
|
[
|
||||||
|
LLMEvent.stepStart({ index: 0 }),
|
||||||
|
LLMEvent.toolCall({ id: "call-terminal", name: "echo", input: { text: "done" } }),
|
||||||
|
LLMEvent.stepFinish({ index: 0, reason: "tool-calls" }),
|
||||||
|
LLMEvent.finish({ reason: "tool-calls" }),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
LLMEvent.stepStart({ index: 0 }),
|
||||||
|
LLMEvent.toolCall({ id: "call-forbidden", name: "echo", input: { text: "forbidden" } }),
|
||||||
|
LLMEvent.stepFinish({ index: 0, reason: "tool-calls" }),
|
||||||
|
LLMEvent.finish({ reason: "tool-calls" }),
|
||||||
|
],
|
||||||
|
]
|
||||||
|
|
||||||
|
yield* session.resume(sessionID)
|
||||||
|
|
||||||
|
expect(requests).toHaveLength(2)
|
||||||
|
expect(requests[0]?.toolChoice).toBeUndefined()
|
||||||
|
expect(requests[1]?.toolChoice).toMatchObject({ type: "none" })
|
||||||
|
expect(requests[1]?.tools).toEqual([])
|
||||||
|
expect(requests[1]?.messages.at(-1)).toMatchObject({
|
||||||
|
role: "assistant",
|
||||||
|
content: [{ type: "text", text: expect.stringContaining("MAXIMUM STEPS REACHED") }],
|
||||||
|
})
|
||||||
|
expect(executions).toEqual(["done"])
|
||||||
|
expect(yield* session.context(sessionID)).toMatchObject([
|
||||||
|
{ type: "user", text: "Finish at the limit" },
|
||||||
|
{ type: "assistant", content: [{ type: "tool", id: "call-terminal", state: { status: "completed" } }] },
|
||||||
|
{ type: "assistant", content: [{ type: "tool", id: "call-forbidden", state: { status: "error" } }] },
|
||||||
|
])
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ import { SessionCompaction } from "./compaction"
|
||||||
import { SystemPrompt } from "./system"
|
import { SystemPrompt } from "./system"
|
||||||
import { Instruction } from "./instruction"
|
import { Instruction } from "./instruction"
|
||||||
import { Plugin } from "../plugin"
|
import { Plugin } from "../plugin"
|
||||||
import MAX_STEPS from "../session/prompt/max-steps.txt"
|
import { MAX_STEPS_PROMPT } from "@opencode-ai/core/session/runner/max-steps"
|
||||||
import { ToolRegistry } from "@/tool/registry"
|
import { ToolRegistry } from "@/tool/registry"
|
||||||
import { MCP } from "../mcp"
|
import { MCP } from "../mcp"
|
||||||
import { LSP } from "@/lsp/lsp"
|
import { LSP } from "@/lsp/lsp"
|
||||||
|
|
@ -1322,7 +1322,7 @@ export const layer = Layer.effect(
|
||||||
sessionID,
|
sessionID,
|
||||||
parentSessionID: session.parentID,
|
parentSessionID: session.parentID,
|
||||||
system,
|
system,
|
||||||
messages: [...modelMsgs, ...(isLastStep ? [{ role: "assistant" as const, content: MAX_STEPS }] : [])],
|
messages: [...modelMsgs, ...(isLastStep ? [{ role: "assistant" as const, content: MAX_STEPS_PROMPT }] : [])],
|
||||||
tools,
|
tools,
|
||||||
model,
|
model,
|
||||||
toolChoice: format.type === "json_schema" ? "required" : undefined,
|
toolChoice: format.type === "json_schema" ? "required" : undefined,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue