Skip to content

Commit 56fa267

Browse files
authored
Merge branch 'dev' into opencode-remote-voice
2 parents b019011 + 2b73a08 commit 56fa267

File tree

3 files changed

+80
-12
lines changed

3 files changed

+80
-12
lines changed

packages/opencode/src/cli/cmd/tui/routes/session/sidebar.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { useSync } from "@tui/context/sync"
33
import { createMemo, Show } from "solid-js"
44
import { useTheme } from "../../context/theme"
55
import { useTuiConfig } from "../../context/tui-config"
6-
import { InstallationVersion } from "@/installation/version"
6+
import { InstallationChannel, InstallationVersion } from "@/installation/version"
77
import { TuiPluginRuntime } from "../../plugin"
88

99
import { getScrollAcceleration } from "../../util/scroll"
@@ -62,6 +62,9 @@ export function Sidebar(props: { sessionID: string; overlay?: boolean }) {
6262
<text fg={theme.text}>
6363
<b>{session()!.title}</b>
6464
</text>
65+
<Show when={InstallationChannel !== "latest"}>
66+
<text fg={theme.textMuted}>{props.sessionID}</text>
67+
</Show>
6568
<Show when={session()!.workspaceID}>
6669
<text fg={theme.textMuted}>
6770
<span style={{ fg: workspaceStatus() === "connected" ? theme.success : theme.error }}></span>{" "}

packages/opencode/src/server/routes/instance/trace.ts

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,31 @@ import { AppRuntime } from "@/effect/app-runtime"
44

55
type AppEnv = Parameters<typeof AppRuntime.runPromise>[0] extends Effect.Effect<any, any, infer R> ? R : never
66

7+
// Build the base span attributes for an HTTP handler: method, path, and every
8+
// matched route param (sessionID, messageID, partID, providerID, ptyID, …)
9+
// prefixed with `opencode.`. This makes each request's root span searchable
10+
// by ID in motel without having to parse the path string.
11+
export interface RequestLike {
12+
readonly req: {
13+
readonly method: string
14+
readonly url: string
15+
param(): Record<string, string>
16+
}
17+
}
18+
19+
export function requestAttributes(c: RequestLike): Record<string, string> {
20+
const attributes: Record<string, string> = {
21+
"http.method": c.req.method,
22+
"http.path": new URL(c.req.url).pathname,
23+
}
24+
for (const [key, value] of Object.entries(c.req.param())) {
25+
attributes[`opencode.${key}`] = value
26+
}
27+
return attributes
28+
}
29+
730
export function runRequest<A, E>(name: string, c: Context, effect: Effect.Effect<A, E, AppEnv>) {
8-
const url = new URL(c.req.url)
9-
return AppRuntime.runPromise(
10-
effect.pipe(
11-
Effect.withSpan(name, {
12-
attributes: {
13-
"http.method": c.req.method,
14-
"http.path": url.pathname,
15-
},
16-
}),
17-
),
18-
)
31+
return AppRuntime.runPromise(effect.pipe(Effect.withSpan(name, { attributes: requestAttributes(c) })))
1932
}
2033

2134
export async function jsonRequest<C extends Context, A, E>(
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { describe, expect, test } from "bun:test"
2+
import { requestAttributes } from "../../src/server/routes/instance/trace"
3+
4+
function fakeContext(method: string, url: string, params: Record<string, string>) {
5+
return {
6+
req: {
7+
method,
8+
url,
9+
param: () => params,
10+
},
11+
}
12+
}
13+
14+
describe("requestAttributes", () => {
15+
test("includes http method and path", () => {
16+
const attrs = requestAttributes(fakeContext("GET", "http://localhost/session", {}))
17+
expect(attrs["http.method"]).toBe("GET")
18+
expect(attrs["http.path"]).toBe("/session")
19+
})
20+
21+
test("strips query string from path", () => {
22+
const attrs = requestAttributes(fakeContext("GET", "http://localhost/file/search?query=foo&limit=10", {}))
23+
expect(attrs["http.path"]).toBe("/file/search")
24+
})
25+
26+
test("tags route params with opencode.<param> prefix", () => {
27+
const attrs = requestAttributes(
28+
fakeContext("GET", "http://localhost/session/ses_abc/message/msg_def/part/prt_ghi", {
29+
sessionID: "ses_abc",
30+
messageID: "msg_def",
31+
partID: "prt_ghi",
32+
}),
33+
)
34+
expect(attrs["opencode.sessionID"]).toBe("ses_abc")
35+
expect(attrs["opencode.messageID"]).toBe("msg_def")
36+
expect(attrs["opencode.partID"]).toBe("prt_ghi")
37+
})
38+
39+
test("produces no param attributes when no params are matched", () => {
40+
const attrs = requestAttributes(fakeContext("POST", "http://localhost/config", {}))
41+
expect(Object.keys(attrs).filter((k) => k.startsWith("opencode."))).toEqual([])
42+
})
43+
44+
test("handles non-ID params (e.g. mcp :name) without mangling", () => {
45+
const attrs = requestAttributes(
46+
fakeContext("POST", "http://localhost/mcp/exa/connect", {
47+
name: "exa",
48+
}),
49+
)
50+
expect(attrs["opencode.name"]).toBe("exa")
51+
})
52+
})

0 commit comments

Comments
 (0)