feat(core): add project reference guidance (#31601)

This commit is contained in:
Dax
2026-06-10 00:08:26 -04:00
committed by GitHub
parent 0fc33e2a06
commit 8a2cfc00c9
38 changed files with 753 additions and 165 deletions
+22 -2
View File
@@ -24,9 +24,13 @@ import { Effect, Context, Layer, Schema } from "effect"
import { InstanceState } from "@/effect/instance-state"
import * as Option from "effect/Option"
import * as OtelTracer from "@effect/opentelemetry/Tracer"
import { type DeepMutable } from "@opencode-ai/core/schema"
import { AbsolutePath, type DeepMutable } from "@opencode-ai/core/schema"
import { ProviderV2 } from "@opencode-ai/core/provider"
import { ModelV2 } from "@opencode-ai/core/model"
import { LocationServiceMap } from "@opencode-ai/core/location-layer"
import { PluginBoot } from "@opencode-ai/core/plugin/boot"
import { Reference } from "@opencode-ai/core/reference"
import { Location } from "@opencode-ai/core/location"
export const Info = Schema.Struct({
name: Schema.String,
@@ -89,15 +93,21 @@ export const layer = Layer.effect(
const plugin = yield* Plugin.Service
const skill = yield* Skill.Service
const provider = yield* Provider.Service
const locations = yield* LocationServiceMap
const state = yield* InstanceState.make<State>(
Effect.fn("Agent.state")(function* (ctx) {
const cfg = yield* config.get()
const skillDirs = yield* skill.dirs()
const referenceDirs = yield* Effect.gen(function* () {
yield* (yield* PluginBoot.Service).wait()
return (yield* (yield* Reference.Service).list()).map((reference) => reference.path)
}).pipe(Effect.provide(locations.get(Location.Ref.make({ directory: AbsolutePath.make(ctx.directory) }))))
const whitelistedDirs = [
Truncate.GLOB,
path.join(Global.Path.tmp, "*"),
...skillDirs.map((dir) => path.join(dir, "*")),
...referenceDirs.map((dir) => path.join(dir, "*")),
]
const readonlyExternalDirectory = {
"*": "ask",
@@ -429,8 +439,18 @@ export const defaultLayer = layer.pipe(
Layer.provide(Auth.defaultLayer),
Layer.provide(Config.defaultLayer),
Layer.provide(Skill.defaultLayer),
Layer.provide(LocationServiceMap.layer),
)
export const node = LayerNode.make(layer, [Config.node, Auth.node, Plugin.node, Skill.node, Provider.node])
const locationServiceMapNode = LayerNode.make(LocationServiceMap.layer, [])
export const node = LayerNode.make(layer, [
Config.node,
Auth.node,
Plugin.node,
Skill.node,
Provider.node,
locationServiceMapNode,
])
export * as Agent from "./agent"
+2 -1
View File
@@ -2,13 +2,14 @@ import { EOL } from "os"
import { Effect } from "effect"
import { FileSystem } from "@opencode-ai/core/filesystem"
import { LocationServiceMap } from "@opencode-ai/core/location-layer"
import { Location } from "@opencode-ai/core/location"
import { AbsolutePath, RelativePath } from "@opencode-ai/core/schema"
import { effectCmd } from "../../effect-cmd"
import { cmd } from "../cmd"
const filesystem = <A, E, R>(effect: Effect.Effect<A, E, R>) =>
effect.pipe(
Effect.provide(LocationServiceMap.get({ directory: AbsolutePath.make(process.cwd()) })),
Effect.provide(LocationServiceMap.get(Location.Ref.make({ directory: AbsolutePath.make(process.cwd()) }))),
Effect.provide(LocationServiceMap.layer),
)
+6 -3
View File
@@ -2,6 +2,7 @@ import { EOL } from "os"
import { Effect, Option } from "effect"
import { Catalog } from "@opencode-ai/core/catalog"
import { LocationServiceMap } from "@opencode-ai/core/location-layer"
import { Location } from "@opencode-ai/core/location"
import { PluginBoot } from "@opencode-ai/core/plugin/boot"
import { AbsolutePath } from "@opencode-ai/core/schema"
import { effectCmd } from "../../effect-cmd"
@@ -37,9 +38,11 @@ export const V2Command = effectCmd({
}).pipe(
Effect.withSpan("Cli.debug.v2"),
Effect.provide(
LocationServiceMap.get({
directory: AbsolutePath.make(process.cwd()),
}),
LocationServiceMap.get(
Location.Ref.make({
directory: AbsolutePath.make(process.cwd()),
}),
),
),
Effect.provide(LocationServiceMap.layer),
),
@@ -18,7 +18,9 @@ export const fileHandlers = HttpApiBuilder.group(InstanceHttpApi, "file", (handl
const filesystem = Effect.fnUntraced(function* <A, E, R>(effect: Effect.Effect<A, E, R>) {
return yield* effect.pipe(
Effect.provide(locations.get({ directory: AbsolutePath.make((yield* InstanceState.context).directory) })),
Effect.provide(
locations.get(Location.Ref.make({ directory: AbsolutePath.make((yield* InstanceState.context).directory) })),
),
)
})
@@ -7,6 +7,7 @@ import { handlePtyInput } from "@opencode-ai/core/pty/input"
import { PtyID } from "@opencode-ai/core/pty/schema"
import { PtyTicket } from "@opencode-ai/core/pty/ticket"
import { LocationServiceMap } from "@opencode-ai/core/location-layer"
import { Location } from "@opencode-ai/core/location"
import { AbsolutePath } from "@opencode-ai/core/schema"
import { Shell } from "@/shell/shell"
import { EffectBridge } from "@/effect/bridge"
@@ -41,13 +42,15 @@ export const ptyHandlers = HttpApiBuilder.group(InstanceHttpApi, "pty", (handler
const cors = yield* CorsConfig
const locations = yield* LocationServiceMap
const unregister = registerDisposer((directory) =>
Effect.runPromise(locations.invalidate({ directory: AbsolutePath.make(directory) })),
Effect.runPromise(locations.invalidate(Location.Ref.make({ directory: AbsolutePath.make(directory) }))),
)
yield* Effect.addFinalizer(() => Effect.sync(unregister))
const pty = Effect.fnUntraced(function* <A, E, R>(effect: Effect.Effect<A, E, R>) {
return yield* effect.pipe(
Effect.provide(locations.get({ directory: AbsolutePath.make((yield* InstanceState.context).directory) })),
Effect.provide(
locations.get(Location.Ref.make({ directory: AbsolutePath.make((yield* InstanceState.context).directory) })),
),
)
})
@@ -158,13 +161,15 @@ export const ptyConnectHandlers = HttpApiBuilder.group(PtyConnectApi, "pty-conne
const cors = yield* CorsConfig
const locations = yield* LocationServiceMap
const unregister = registerDisposer((directory) =>
Effect.runPromise(locations.invalidate({ directory: AbsolutePath.make(directory) })),
Effect.runPromise(locations.invalidate(Location.Ref.make({ directory: AbsolutePath.make(directory) }))),
)
yield* Effect.addFinalizer(() => Effect.sync(unregister))
const pty = Effect.fnUntraced(function* <A, E, R>(effect: Effect.Effect<A, E, R>) {
return yield* effect.pipe(
Effect.provide(locations.get({ directory: AbsolutePath.make((yield* InstanceState.context).directory) })),
Effect.provide(
locations.get(Location.Ref.make({ directory: AbsolutePath.make((yield* InstanceState.context).directory) })),
),
)
})
+33 -3
View File
@@ -16,6 +16,11 @@ import type { Provider } from "@/provider/provider"
import type { Agent } from "@/agent/agent"
import { Permission } from "@/permission"
import { Skill } from "@/skill"
import { AbsolutePath } from "@opencode-ai/core/schema"
import { Location } from "@opencode-ai/core/location"
import { LocationServiceMap } from "@opencode-ai/core/location-layer"
import { PluginBoot } from "@opencode-ai/core/plugin/boot"
import { Reference } from "@opencode-ai/core/reference"
export function provider(model: Provider.Model) {
if (model.api.id.includes("gpt-4") || model.api.id.includes("o1") || model.api.id.includes("o3"))
@@ -44,10 +49,15 @@ export const layer = Layer.effect(
Service,
Effect.gen(function* () {
const skill = yield* Skill.Service
const locations = yield* LocationServiceMap
return Service.of({
environment: Effect.fn("SystemPrompt.environment")(function* (model: Provider.Model) {
const ctx = yield* InstanceState.context
const references = yield* Effect.gen(function* () {
yield* (yield* PluginBoot.Service).wait()
return (yield* (yield* Reference.Service).list()).filter((reference) => reference.description !== undefined)
}).pipe(Effect.provide(locations.get(Location.Ref.make({ directory: AbsolutePath.make(ctx.directory) }))))
return [
[
`You are powered by the model named ${model.api.id}. The exact model ID is ${model.providerID}/${model.api.id}`,
@@ -60,7 +70,25 @@ export const layer = Layer.effect(
` Today's date: ${new Date().toDateString()}`,
`</env>`,
].join("\n"),
]
references.length === 0
? undefined
: [
"Project references provide additional directories that can be accessed when relevant.",
"<available_references>",
...references
.toSorted((a, b) => a.name.localeCompare(b.name))
.flatMap((reference) => [
" <reference>",
` <name>${reference.name}</name>`,
` <path>${reference.path}</path>`,
...(reference.description === undefined
? []
: [` <description>${reference.description}</description>`]),
" </reference>",
]),
"</available_references>",
].join("\n"),
].filter((part): part is string => part !== undefined)
}),
skills: Effect.fn("SystemPrompt.skills")(function* (agent: Agent.Info) {
@@ -80,8 +108,10 @@ export const layer = Layer.effect(
}),
)
export const defaultLayer = layer.pipe(Layer.provide(Skill.defaultLayer))
export const defaultLayer = layer.pipe(Layer.provide(Skill.defaultLayer), Layer.provide(LocationServiceMap.layer))
export const node = LayerNode.make(layer, [Skill.node])
const locationServiceMapNode = LayerNode.make(LocationServiceMap.layer, [])
export const node = LayerNode.make(layer, [Skill.node, locationServiceMapNode])
export * as SystemPrompt from "./system"