feat(plugin): expose app metadata (#38179)
This commit is contained in:
parent
2ed8fe5960
commit
8de40be6ea
71 changed files with 477 additions and 286 deletions
8
packages/core/test/app.test.ts
Normal file
8
packages/core/test/app.test.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import { expect, test } from "bun:test"
|
||||
import { App } from "@opencode-ai/core/app"
|
||||
|
||||
test("formats app metadata as a user agent", () => {
|
||||
expect(App.useragent(App.make({ name: "sdk", version: "1.2.3", channel: "beta" }))).toBe(
|
||||
"opencode/beta/1.2.3/sdk",
|
||||
)
|
||||
})
|
||||
|
|
@ -39,12 +39,13 @@ describe("resource", () => {
|
|||
process.env.OTEL_RESOURCE_ATTRIBUTES =
|
||||
"opencode.client=web,service.instance.id=override,service.namespace=anomalyco"
|
||||
|
||||
expect(resource("cli").attributes).toMatchObject({
|
||||
const app = { client: "cli", version: "1.2.3", channel: "beta" }
|
||||
expect(resource(app).attributes).toMatchObject({
|
||||
"opencode.client": "cli",
|
||||
"service.namespace": "anomalyco",
|
||||
})
|
||||
expect(resource("cli").attributes["service.instance.id"]).not.toBe("override")
|
||||
expect(resource("cli").attributes["opencode.run"]).toMatch(/^[0-9a-f]{8}$/)
|
||||
expect(resource(app).attributes["service.instance.id"]).not.toBe("override")
|
||||
expect(resource(app).attributes["opencode.run"]).toMatch(/^[0-9a-f]{8}$/)
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -136,6 +136,7 @@ function resourceServer(
|
|||
return {
|
||||
state,
|
||||
url: http.url.toString(),
|
||||
clientVersion: () => protocol.getClientVersion(),
|
||||
sendResourceListChanged: () => protocol.sendResourceListChanged(),
|
||||
completeElicitation: () => protocol.createElicitationCompletionNotifier("elicitation-test")(),
|
||||
close: async () => {
|
||||
|
|
@ -151,10 +152,11 @@ function resourceServer(
|
|||
function resourceMcpLayer(
|
||||
server: string | typeof ConfigMCP.Server.Type,
|
||||
onFormCreated?: (form: Form.Info) => Effect.Effect<void>,
|
||||
options?: MCP.Options,
|
||||
) {
|
||||
const directory = AbsolutePath.make(import.meta.dir)
|
||||
const unusedIntegration = () => Effect.die("unused integration service")
|
||||
return MCP.layer.pipe(
|
||||
return MCP.layer(options).pipe(
|
||||
Layer.provideMerge(Form.layer),
|
||||
Layer.provide(
|
||||
Layer.mergeAll(
|
||||
|
|
@ -643,7 +645,12 @@ test("loads and reads MCP resources", async () => {
|
|||
{ type: "blob", uri: "docs://logo", blob: "aGVsbG8=", mimeType: "image/png" },
|
||||
],
|
||||
})
|
||||
}).pipe(Effect.provide(resourceMcpLayer(server.url)))
|
||||
expect(server.clientVersion()).toMatchObject({ name: "sdk", version: "1.2.3" })
|
||||
}).pipe(
|
||||
Effect.provide(
|
||||
resourceMcpLayer(server.url, undefined, { clientInfo: { name: "sdk", version: "1.2.3" } }),
|
||||
),
|
||||
)
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -302,7 +302,7 @@ describe("ModelsDev Service", () => {
|
|||
const final = yield* Ref.get(state)
|
||||
expect(final.calls.length).toBe(1)
|
||||
expect(final.calls[0].url).toContain("/api.json")
|
||||
expect(final.calls[0].userAgent).toContain("/cli")
|
||||
expect(final.calls[0].userAgent).toContain("/opencode")
|
||||
}),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ type Overrides = Partial<Omit<PluginContext, "options" | "session">> & {
|
|||
|
||||
export function host(overrides: Overrides = {}): PluginContext {
|
||||
return {
|
||||
app: overrides.app ?? { name: "test", version: "test", channel: "test" },
|
||||
options: {},
|
||||
agent: overrides.agent ?? {
|
||||
get: () => Effect.die("unused agent.get"),
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { AISDK } from "@opencode-ai/core/aisdk"
|
||||
import { App } from "@opencode-ai/core/app"
|
||||
import { describe, expect } from "bun:test"
|
||||
import { Effect } from "effect"
|
||||
import { Catalog } from "@opencode-ai/core/catalog"
|
||||
|
|
@ -62,6 +63,7 @@ describe("GithubCopilotPlugin", () => {
|
|||
return Response.json({ ok: true })
|
||||
},
|
||||
false,
|
||||
App.make({ name: "test", version: "1.2.3", channel: "beta" }),
|
||||
)
|
||||
yield* Effect.promise(() =>
|
||||
send("https://api.githubcopilot.com/chat/completions", {
|
||||
|
|
@ -77,6 +79,7 @@ describe("GithubCopilotPlugin", () => {
|
|||
expect(requests[0]?.get("x-initiator")).toBe("user")
|
||||
expect(requests[0]?.get("copilot-vision-request")).toBe("true")
|
||||
expect(requests[0]?.get("x-github-api-version")).toBe("2026-06-01")
|
||||
expect(requests[0]?.get("user-agent")).toBe("opencode/beta/1.2.3/test")
|
||||
}),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import { NodeFileSystem } from "@effect/platform-node"
|
|||
import { Config } from "@opencode-ai/core/config"
|
||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||
import { FSUtil } from "@opencode-ai/util/fs-util"
|
||||
import { InstallationVersion } from "@opencode-ai/util/installation/version"
|
||||
import { Location } from "@opencode-ai/core/location"
|
||||
import { Effect } from "effect"
|
||||
import { SkillPlugin } from "@opencode-ai/core/plugin/skill"
|
||||
|
|
@ -21,6 +20,7 @@ describe("SkillPlugin.Plugin", () => {
|
|||
const skill = yield* SkillV2.Service
|
||||
yield* SkillPlugin.Plugin.effect(
|
||||
host({
|
||||
app: { name: "test", version: "1.2.3", channel: "beta" },
|
||||
skill: {
|
||||
list: () => Effect.die("unused skill.list"),
|
||||
transform: skill.transform,
|
||||
|
|
@ -54,7 +54,8 @@ describe("SkillPlugin.Plugin", () => {
|
|||
}),
|
||||
)
|
||||
expect(report?.slash).toBe(true)
|
||||
expect(report?.content).toContain(`- opencode version: ${InstallationVersion}`)
|
||||
expect(report?.content).toContain("- opencode version: 1.2.3")
|
||||
expect(report?.content).toContain("- install/channel: beta")
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import { SessionStore } from "@opencode-ai/core/session/store"
|
|||
import { SessionV2 } from "@opencode-ai/core/session"
|
||||
import { Project } from "@opencode-ai/core/project"
|
||||
import { ProjectTable } from "@opencode-ai/core/project/sql"
|
||||
import { InstallationVersion } from "@opencode-ai/util/installation/version"
|
||||
import { App } from "@opencode-ai/core/app"
|
||||
import { AbsolutePath } from "@opencode-ai/core/schema"
|
||||
import { Money } from "@opencode-ai/schema/money"
|
||||
import { DateTime, Effect, Fiber, Layer, Stream } from "effect"
|
||||
|
|
@ -192,10 +192,10 @@ it.effect("manual compaction summarizes short context instead of no-op", () =>
|
|||
"x-session-affinity": sessionID,
|
||||
"X-Session-Id": sessionID,
|
||||
"x-parent-session-id": parentID,
|
||||
"User-Agent": `opencode/${InstallationVersion}`,
|
||||
"User-Agent": App.useragent(App.make()),
|
||||
"x-opencode-project": Project.ID.global,
|
||||
"x-opencode-session": sessionID,
|
||||
"x-opencode-client": "cli",
|
||||
"x-opencode-client": "opencode",
|
||||
})
|
||||
expect(requests[0]?.generation).toBeUndefined()
|
||||
expect(JSON.stringify(requests[0]?.messages)).toContain("Manual compaction should include this short conversation.")
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
|||
import { LayerNodePlatform } from "@opencode-ai/core/effect/app-node-platform"
|
||||
import { LayerNode } from "@opencode-ai/util/effect/layer-node"
|
||||
import { EventV2 } from "@opencode-ai/core/event"
|
||||
import { InstallationVersion } from "@opencode-ai/util/installation/version"
|
||||
import { App } from "@opencode-ai/core/app"
|
||||
import { PermissionV2 } from "@opencode-ai/core/permission"
|
||||
import { EventTable } from "@opencode-ai/core/event/sql"
|
||||
import { Project } from "@opencode-ai/core/project"
|
||||
|
|
@ -3180,10 +3180,10 @@ describe("SessionRunnerLLM", () => {
|
|||
expect(requests[0]?.http?.headers).toEqual({
|
||||
"x-session-affinity": sessionID,
|
||||
"X-Session-Id": sessionID,
|
||||
"User-Agent": `opencode/${InstallationVersion}`,
|
||||
"User-Agent": App.useragent(App.make()),
|
||||
"x-opencode-project": Project.ID.global,
|
||||
"x-opencode-session": sessionID,
|
||||
"x-opencode-client": "cli",
|
||||
"x-opencode-client": "opencode",
|
||||
})
|
||||
}),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import { SessionTitle } from "@opencode-ai/core/session/title"
|
|||
import { SessionV2 } from "@opencode-ai/core/session"
|
||||
import { Project } from "@opencode-ai/core/project"
|
||||
import { ProjectTable } from "@opencode-ai/core/project/sql"
|
||||
import { InstallationVersion } from "@opencode-ai/util/installation/version"
|
||||
import { App } from "@opencode-ai/core/app"
|
||||
import { AbsolutePath } from "@opencode-ai/core/schema"
|
||||
import { Money } from "@opencode-ai/schema/money"
|
||||
import { Effect, Layer, Stream } from "effect"
|
||||
|
|
@ -155,10 +155,10 @@ it.effect("generates a title from the sole user message and renames the session"
|
|||
expect(requests[0]?.http?.headers).toEqual({
|
||||
"x-session-affinity": sessionID,
|
||||
"X-Session-Id": sessionID,
|
||||
"User-Agent": `opencode/${InstallationVersion}`,
|
||||
"User-Agent": App.useragent(App.make()),
|
||||
"x-opencode-project": Project.ID.global,
|
||||
"x-opencode-session": sessionID,
|
||||
"x-opencode-client": "cli",
|
||||
"x-opencode-client": "opencode",
|
||||
})
|
||||
expect(JSON.stringify(requests[0]?.messages)).toContain("Help me debug the failing build")
|
||||
const renamed = yield* store.get(sessionID)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue