diff --git a/packages/core/src/plugin/provider/openai.ts b/packages/core/src/plugin/provider/openai.ts index 255cbb2cfcc..5c7bdc38aff 100644 --- a/packages/core/src/plugin/provider/openai.ts +++ b/packages/core/src/plugin/provider/openai.ts @@ -1,5 +1,6 @@ import type { IntegrationOAuthMethodRegistration } from "@opencode/plugin/effect/integration" import { define } from "@opencode/plugin/effect/plugin" +import type { SessionRequest } from "@opencode/plugin/effect/session" import { Deferred, Effect, Option, Schema, Semaphore, Stream } from "effect" import type { Server } from "node:http" import { App } from "../../app.js" @@ -307,6 +308,13 @@ export const OpenAIPlugin = define({ }), { providerID: Provider.ID.openai }, ) + // The ChatGPT backend rejects a requested output limit, and OpenAI counts one against rate limits. + const omitOutputLimit = (evt: SessionRequest) => + Effect.sync(() => { + delete evt.options.maxTokens + }) + for (const name of ["context", "compaction"] as const) + yield* ctx.session.hook(name, omitOutputLimit, { providerID: Provider.ID.openai }) const refresh = () => loading.withPermit(load().pipe(Effect.andThen(ctx.provider.reload()))) yield* bus.subscribe(Credential.Event.Switched).pipe( Stream.filter((event) => event.data.integrationID === Integration.ID.make("openai")), diff --git a/packages/core/src/session/compaction.ts b/packages/core/src/session/compaction.ts index fc9d16799a0..09edc54bd4a 100644 --- a/packages/core/src/session/compaction.ts +++ b/packages/core/src/session/compaction.ts @@ -10,12 +10,13 @@ import { LLMRequest, Message, type ContentPart, + type ToolResultPart, type Usage, } from "@opencode/ai" import type { StreamOptions } from "@opencode/ai/route" import type { SessionCompactionResult } from "@opencode/plugin/effect/session" import { SessionError } from "@opencode/schema/session-error" -import { Context, Effect, Layer, Stream } from "effect" +import { Context, Effect, Layer, Ref, Stream } from "effect" import { Bus } from "../bus.js" import { Database } from "../database/database.js" import { makeLocationNode } from "@opencode/util/effect/app-node" @@ -37,12 +38,17 @@ import { toLLMMessages } from "./runner/to-llm-message.js" import type { AgentNotFoundError } from "./error.js" import type { Instructions } from "../instructions/index.js" -const DEFAULT_BUFFER = 20_000 +const AUTO_THRESHOLD = 0.9 const DEFAULT_KEEP_TOKENS = 15_000 -const OUTPUT_TOKEN_MAX = 32_000 const TOOL_OUTPUT_MAX_CHARS = 2_000 +const FALLBACK_TOOL_CHARS = 1_000 +const FALLBACK_OVERFLOW_RETRIES = 3 const IMAGE_TOKEN_ESTIMATE = 1_500 const PDF_TOKEN_ESTIMATE = 2_000 +const usableWindow = (limit: SessionRunnerModel.Resolved["limit"]) => + Math.min(limit.context > 0 ? limit.context : (limit.input ?? 0), limit.input ?? Number.POSITIVE_INFINITY) +const compactionCeiling = (usable: number, buffer?: number) => + buffer === undefined ? Math.floor(usable * AUTO_THRESHOLD) : usable - buffer const SUMMARY_TEMPLATE = `You MUST use this format for your response (you may omit sections that aren't applicable). Do not include the