mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-21 22:23:29 +00:00
chore(app): use schema ID minting instead of hand-rolled encoder (#43542)
This commit is contained in:
parent
b6966177fa
commit
30db9dd86e
4 changed files with 10 additions and 108 deletions
|
|
@ -417,7 +417,9 @@ describe("prompt submit worktree selection", () => {
|
|||
model: { providerID: "provider", modelID: "model", variant: "high" },
|
||||
},
|
||||
})
|
||||
expect((promptInputs[0] as { id?: string }).id).toStartWith("msg_")
|
||||
// ID minting is delegated to the data layer, which mints a client ID when
|
||||
// none is supplied (covered by the data-layer tests in packages/tui).
|
||||
expect((promptInputs[0] as { id?: string }).id).toBeUndefined()
|
||||
})
|
||||
|
||||
test("restores the prompt when sending fails", async () => {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import { usePermission } from "@/context/permission"
|
|||
import { type ContextItem, type ImageAttachmentPart, type Prompt, type usePrompt } from "@/context/prompt"
|
||||
import { useWorkspaceLocation } from "@/context/location"
|
||||
import { useServerSDK, type ServerSDK } from "@/context/server-sdk"
|
||||
import { Identifier } from "@/utils/id"
|
||||
import { SessionMessage } from "@opencode-ai/schema/session-message"
|
||||
import { getDirectory } from "@opencode-ai/util/path"
|
||||
import { buildPromptRequest } from "./build-prompt-request"
|
||||
import { setCursorPosition } from "./editor-dom"
|
||||
|
|
@ -40,7 +40,6 @@ type FollowupSendInput = {
|
|||
data: Data
|
||||
session: Accessor<{ agent?: string; model?: { id: string; providerID: string; variant?: string } } | undefined>
|
||||
draft: FollowupDraft
|
||||
messageID?: string
|
||||
optimisticBusy?: boolean
|
||||
}
|
||||
|
||||
|
|
@ -69,10 +68,9 @@ export async function sendFollowupDraft(input: FollowupSendInput) {
|
|||
) {
|
||||
setBusy()
|
||||
try {
|
||||
const messageID = Identifier.ascending("message")
|
||||
await input.api.command({
|
||||
sessionID: input.draft.sessionID,
|
||||
id: messageID,
|
||||
id: SessionMessage.ID.create(),
|
||||
command: cmd,
|
||||
arguments: tail.join(" "),
|
||||
agent: input.draft.agent,
|
||||
|
|
@ -95,7 +93,6 @@ export async function sendFollowupDraft(input: FollowupSendInput) {
|
|||
}
|
||||
}
|
||||
|
||||
const messageID = input.messageID ?? Identifier.ascending("message")
|
||||
const encodedImages = await Promise.all(
|
||||
images.map(async (attachment) => ({
|
||||
...attachment,
|
||||
|
|
@ -132,11 +129,10 @@ export async function sendFollowupDraft(input: FollowupSendInput) {
|
|||
})
|
||||
}
|
||||
|
||||
// The data layer admits optimistically: the prompt renders immediately
|
||||
// and rolls back if the server rejects it.
|
||||
// The data layer admits optimistically under a client-minted ID: the
|
||||
// prompt renders immediately and rolls back if the server rejects it.
|
||||
await input.data.session.prompt({
|
||||
sessionID: input.draft.sessionID,
|
||||
id: messageID,
|
||||
text: request.text,
|
||||
files: request.files.map((file) => ({ uri: file.uri, name: file.name, mention: file.mention })),
|
||||
agents: request.agents,
|
||||
|
|
@ -448,12 +444,11 @@ export function createPromptSubmit(input: PromptSubmitInput) {
|
|||
?.find((command) => command.name === commandName)
|
||||
if (customCommand) {
|
||||
clearInput()
|
||||
const messageID = Identifier.ascending("message")
|
||||
submissionData.session.setStatus(session.id, "running")
|
||||
void submissionServerSDK.api.session
|
||||
.command({
|
||||
sessionID: session.id,
|
||||
id: messageID,
|
||||
id: SessionMessage.ID.create(),
|
||||
command: commandName,
|
||||
arguments: args.join(" "),
|
||||
agent,
|
||||
|
|
@ -478,7 +473,6 @@ export function createPromptSubmit(input: PromptSubmitInput) {
|
|||
}
|
||||
|
||||
const commentItems = context.filter((item) => item.type === "file" && !!item.comment?.trim())
|
||||
const messageID = Identifier.ascending("message")
|
||||
|
||||
for (const item of commentItems) submission.target().context.remove(item.key)
|
||||
clearInput()
|
||||
|
|
@ -488,7 +482,6 @@ export function createPromptSubmit(input: PromptSubmitInput) {
|
|||
data: submissionData,
|
||||
session: () => session,
|
||||
draft,
|
||||
messageID,
|
||||
optimisticBusy: sessionDirectory === projectDirectory,
|
||||
}).catch((err) => {
|
||||
if (sessionDirectory === projectDirectory) {
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ import { TerminalPanelV2 } from "@/pages/session/terminal-panel-v2"
|
|||
import { useComposerCommands } from "@/pages/session/use-composer-commands"
|
||||
import { useSessionCommands } from "@/pages/session/use-session-commands"
|
||||
import { useSessionHashScroll } from "@/pages/session/use-session-hash-scroll"
|
||||
import { Identifier } from "@/utils/id"
|
||||
import { SessionMessage } from "@opencode-ai/schema/session-message"
|
||||
import { Persist, persisted } from "@/utils/persist"
|
||||
import { formatServerError, isLocalSessionNotFoundError, isSessionNotFoundError } from "@/utils/server-errors"
|
||||
import { requireServerKey, sessionHref } from "@/utils/session-route"
|
||||
|
|
@ -1564,7 +1564,7 @@ export default function Page() {
|
|||
const queueFollowup = (draft: FollowupDraft) => {
|
||||
setFollowup("items", draft.sessionID, (items) => [
|
||||
...(items ?? []),
|
||||
{ id: Identifier.ascending("message"), ...draft },
|
||||
{ id: SessionMessage.ID.create(), ...draft },
|
||||
])
|
||||
setFollowup("failed", draft.sessionID, undefined)
|
||||
setFollowup("paused", draft.sessionID, undefined)
|
||||
|
|
|
|||
|
|
@ -1,93 +0,0 @@
|
|||
const prefixes = {
|
||||
session: "ses",
|
||||
message: "msg",
|
||||
permission: "per",
|
||||
user: "usr",
|
||||
part: "prt",
|
||||
pty: "pty",
|
||||
} as const
|
||||
|
||||
const LENGTH = 26
|
||||
let lastTimestamp = 0
|
||||
let counter = 0
|
||||
|
||||
type Prefix = keyof typeof prefixes
|
||||
export namespace Identifier {
|
||||
export function ascending(prefix: Prefix, given?: string) {
|
||||
return generateID(prefix, false, given)
|
||||
}
|
||||
|
||||
export function descending(prefix: Prefix, given?: string) {
|
||||
return generateID(prefix, true, given)
|
||||
}
|
||||
}
|
||||
|
||||
function generateID(prefix: Prefix, descending: boolean, given?: string): string {
|
||||
if (!given) {
|
||||
return create(prefix, descending)
|
||||
}
|
||||
|
||||
if (!given.startsWith(prefixes[prefix])) {
|
||||
throw new Error(`ID ${given} does not start with ${prefixes[prefix]}`)
|
||||
}
|
||||
|
||||
return given
|
||||
}
|
||||
|
||||
function create(prefix: Prefix, descending: boolean, timestamp?: number): string {
|
||||
const currentTimestamp = timestamp ?? Date.now()
|
||||
|
||||
if (currentTimestamp !== lastTimestamp) {
|
||||
lastTimestamp = currentTimestamp
|
||||
counter = 0
|
||||
}
|
||||
|
||||
counter += 1
|
||||
|
||||
let now = BigInt(currentTimestamp) * BigInt(0x1000) + BigInt(counter)
|
||||
|
||||
if (descending) {
|
||||
now = ~now
|
||||
}
|
||||
|
||||
const timeBytes = new Uint8Array(6)
|
||||
for (let i = 0; i < 6; i += 1) {
|
||||
timeBytes[i] = Number((now >> BigInt(40 - 8 * i)) & BigInt(0xff))
|
||||
}
|
||||
|
||||
return prefixes[prefix] + "_" + bytesToHex(timeBytes) + randomBase62(LENGTH - 12)
|
||||
}
|
||||
|
||||
function bytesToHex(bytes: Uint8Array): string {
|
||||
let hex = ""
|
||||
for (let i = 0; i < bytes.length; i += 1) {
|
||||
hex += bytes[i].toString(16).padStart(2, "0")
|
||||
}
|
||||
return hex
|
||||
}
|
||||
|
||||
function randomBase62(length: number): string {
|
||||
const chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
||||
const bytes = getRandomBytes(length)
|
||||
let result = ""
|
||||
for (let i = 0; i < length; i += 1) {
|
||||
result += chars[bytes[i] % 62]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
function getRandomBytes(length: number): Uint8Array {
|
||||
const bytes = new Uint8Array(length)
|
||||
const cryptoObj = typeof globalThis !== "undefined" ? globalThis.crypto : undefined
|
||||
|
||||
if (cryptoObj && typeof cryptoObj.getRandomValues === "function") {
|
||||
cryptoObj.getRandomValues(bytes)
|
||||
return bytes
|
||||
}
|
||||
|
||||
for (let i = 0; i < length; i += 1) {
|
||||
bytes[i] = Math.floor(Math.random() * 256)
|
||||
}
|
||||
|
||||
return bytes
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue