How do I fix this typing error in this file?
- @packages/console/core/drizzle.config.ts
- @packages/console/core/sst-env.d.ts
How do I fix this typing error in this file?
<file>
00001| import { Resource } from "./sst-env.d.ts"
00002| import { defineConfig } from "drizzle-kit"
00003|
00004| export default defineConfig({
00005| out: "./migrations/",
00006| strict: true,
00007| schema: ["./src/**/*.sql.ts"],
00008| verbose: true,
00009| dialect: "mysql",
00010| dbCredentials: {
00011| database: Resource.Database.database,
00012| host: Resource.Database.host,
00013| user: Resource.Database.username,
00014| password: Resource.Database.password,
00015| port: Resource.Database.port,
00016| ssl: {
00017| rejectUnauthorized: false,
00018| },
00019| },
00020| })
00021|
(End of file - total 21 lines)
</file><file>
00001| /* This file is auto-generated by SST. Do not edit. */
00002| /* tslint:disable */
00003| /* eslint-disable */
00004| /* deno-fmt-ignore-file */
00005|
00006| import "sst"
00007| declare module "sst" {
00008| export interface Resource {
00009| "ADMIN_SECRET": {
00010| "type": "sst.sst.Secret"
00011| "value": string
00012| }
00013| "AUTH_API_URL": {
00014| "type": "sst.sst.Linkable"
00015| "value": string
00016| }
00017| "AWS_SES_ACCESS_KEY_ID": {
00018| "type": "sst.sst.Secret"
00019| "value": string
00020| }
00021| "AWS_SES_SECRET_ACCESS_KEY": {
00022| "type": "sst.sst.Secret"
00023| "value": string
00024| }
00025| "CLOUDFLARE_API_TOKEN": {
00026| "type": "sst.sst.Secret"
00027| "value": string
00028| }
00029| "CLOUDFLARE_DEFAULT_ACCOUNT_ID": {
00030| "type": "sst.sst.Secret"
00031| "value": string
00032| }
00033| "Console": {
00034| "type": "sst.cloudflare.SolidStart"
00035| "url": string
00036| }
00037| "DISCORD_SUPPORT_BOT_TOKEN": {
00038| "type": "sst.sst.Secret"
00039| "value": string
00040| }
00041| "DISCORD_SUPPORT_CHANNEL_ID": {
00042| "type": "sst.sst.Secret"
00043| "value": string
00044| }
00045| "Database": {
00046| "database": string
00047| "host": string
00048| "password": string
00049| "port": number
00050| "type": "sst.sst.Linkable"
00051| "username": string
00052| }
00053| "EMAILOCTOPUS_API_KEY": {
00054| "type": "sst.sst.Secret"
00055| "value": string
00056| }
00057| "FEISHU_APP_ID": {
00058| "type": "sst.sst.Secret"
00059| "value": string
00060| }
00061| "FEISHU_APP_SECRET": {
00062| "type": "sst.sst.Secret"
00063| "value": string
00064| }
00065| "GITHUB_APP_ID": {
00066| "type": "sst.sst.Secret"
00067| "value": string
00068| }
0006...Found 9 matches
/home/jm/data/code/opencode/packages/console/core/drizzle.config.ts:
Line 4: export default defineConfig({
Line 11: database: Resource.Database.database,
Line 12: host: Resource.Database.host,
Line 13: user: Resource.Database.username,
Line 14: password: Resource.Database.password,
Line 15: port: Resource.Database.port,
/home/jm/data/code/opencode/packages/console/core/src/drizzle/index.ts:
Line 22: host: Resource.Database.host,
Line 23: username: Resource.Database.username,
Line 24: password: Resource.Database.password,<file>
00001| import { drizzle } from "drizzle-orm/planetscale-serverless"
00002| import { Resource } from "@opencode-ai/console-resource"
00003| export * from "drizzle-orm"
00004| import { Client } from "@planetscale/database"
00005|
00006| import { MySqlTransaction, type MySqlTransactionConfig } from "drizzle-orm/mysql-core"
00007| import type { ExtractTablesWithRelations } from "drizzle-orm"
00008| import type { PlanetScalePreparedQueryHKT, PlanetscaleQueryResultHKT } from "drizzle-orm/planetscale-serverless"
00009| import { Context } from "../context"
00010| import { memo } from "../util/memo"
00011|
00012| export namespace Database {
00013| export type Transaction = MySqlTransaction<
00014| PlanetscaleQueryResultHKT,
00015| PlanetScalePreparedQueryHKT,
00016| Record<string, never>,
00017| ExtractTablesWithRelations<Record<string, never>>
00018| >
00019|
00020| const client = memo(() => {
00021| const result = new Client({
00022| host: Resource.Database.host,
00023| username: Resource.Database.username,
00024| password: Resource.Database.password,
00025| })
00026| const db = drizzle(result, {})
00027| return db
00028| })
00029|
00030| export type TxOrDb = Transaction | ReturnType<typeof client>
00031|
00032| const TransactionContext = Context.create<{
00033| tx: TxOrDb
00034| effects: (() => void | Promise<void>)[]
00035| }>()
00036|
00037| export async function use<T>(callback: (trx: TxOrDb) => Promise<T>) {
00038| try {
00039| const { tx } = TransactionContext.use()
00040| return tx.transaction(callback)
00041| } catch (err) {
00042| if (err instanceof Context.NotFound) {
00043| const effects: (() => void | Promise<void>)[] = []
00044| const result = await TransactionContext.provide(
00045| {
00046| effects,
00047| tx: client(),
00048| },
00049| () => callback(client()),
00050| ).../home/jm/data/code/opencode/packages/console/core/tsconfig.jsonNo files foundFound 14 matches
/home/jm/data/code/opencode/packages/console/core/drizzle.config.ts:
Line 1: import { Resource } from "./sst-env.d.ts"
/home/jm/data/code/opencode/packages/console/function/src/log-processor.ts:
Line 1: import { Resource } from "@opencode-ai/console-resource"
/home/jm/data/code/opencode/packages/console/function/src/auth.ts:
Line 13: import { Resource } from "@opencode-ai/console-resource"
/home/jm/data/code/opencode/packages/console/core/src/model.ts:
Line 8: import { Resource } from "@opencode-ai/console-resource"
/home/jm/data/code/opencode/packages/console/core/src/black.ts:
Line 3: import { Resource } from "@opencode-ai/console-resource"
/home/jm/data/code/opencode/packages/console/core/src/billing.ts:
Line 7: import { Resource } from "@opencode-ai/console-resource"
/home/jm/data/code/opencode/packages/console/core/src/aws.ts:
Line 2: import { Resource } from "@opencode-ai/console-resource"
/home/jm/data/code/opencode/packages/console/core/src/drizzle/index.ts:
Line 2: import { Resource } from "@opencode-ai/console-resource"
/home/jm/data/code/opencode/packages/console/core/script/reset-db.ts:
Line 1: import { Resource } from "@opencode-ai/console-resource"
/home/jm/data/code/opencode/packages/console/app/src/routes/zen/util/logger.ts:
Line 1: import { Resource } from "@opencode-ai/console-resource"
/home/jm/data/code/opencode/packages/console/app/src/routes/zen/util/stickyProviderTracker.ts:
Line 1: import { Resource } from "@opencode-ai/console-resource"
/home/jm/data/code/opencode/packages/console/app/src/routes/zen/util/dataDumper.ts:
Line 1: import { Resource, waitUntil } from "@opencode-ai/console-resource"
/home/jm/data/code/opencode/packages/console/app/src/routes/stripe/webhook.ts:
Line 8: import { Resource } from "@opencode-ai/console-resource"
/home/jm/data/code/opencode/packages/console/app/src/context/auth.ts:
Line 15: import { Resource } from "@opencode-ai/console-resource"<file>
00001| {
00002| "$schema": "https://json.schemastore.org/tsconfig",
00003| "extends": "@tsconfig/node22/tsconfig.json",
00004| "compilerOptions": {
00005| "module": "ESNext",
00006| "moduleResolution": "bundler",
00007| "jsx": "preserve",
00008| "jsxImportSource": "react",
00009| "types": ["@cloudflare/workers-types", "node"]
00010| }
00011| }
00012|
(End of file - total 12 lines)
</file><file>
00001| {
00002| "$schema": "https://json.schemastore.org/package.json",
00003| "name": "@opencode-ai/console-core",
00004| "version": "1.1.53",
00005| "private": true,
00006| "type": "module",
00007| "license": "MIT",
00008| "dependencies": {
00009| "@aws-sdk/client-sts": "3.782.0",
00010| "@jsx-email/render": "1.1.1",
00011| "@opencode-ai/console-mail": "workspace:*",
00012| "@opencode-ai/console-resource": "workspace:*",
00013| "@planetscale/database": "1.19.0",
00014| "aws4fetch": "1.0.20",
00015| "drizzle-orm": "0.41.0",
00016| "postgres": "3.4.7",
00017| "stripe": "18.0.0",
00018| "ulid": "catalog:",
00019| "zod": "catalog:"
00020| },
00021| "exports": {
00022| "./*": "./src/*"
00023| },
00024| "scripts": {
00025| "db": "sst shell drizzle-kit",
00026| "db-dev": "sst shell --stage dev -- drizzle-kit",
00027| "db-prod": "sst shell --stage production -- drizzle-kit",
00028| "shell": "sst shell",
00029| "shell-dev": "sst shell --stage dev",
00030| "shell-prod": "sst shell --stage production",
00031| "update-models": "script/update-models.ts",
00032| "promote-models-to-dev": "script/promote-models.ts dev",
00033| "promote-models-to-prod": "script/promote-models.ts production",
00034| "pull-models-from-dev": "script/pull-models.ts dev",
00035| "pull-models-from-prod": "script/pull-models.ts production",
00036| "update-black": "script/update-black.ts",
00037| "promote-black-to-dev": "script/promote-black.ts dev",
00038| "promote-black-to-prod": "script/promote-black.ts production",
00039| "typecheck": "tsgo --noEmit"
00040| },
00041| "devDependencies": {
00042| "@cloudflare/workers-types": "catalog:",
00043| "@tsconfig/node22": "22.0.2",
00044| "@types/bun": "1.3.0",
00045| "@types/node": "catalog:",
00046| "drizzle-kit": "0.30.5",
00047| "mysql2": "3.14.4",
00048| "typescript": "catalog:",
00049| ...No files foundNo files founderror: No packages matched the filter
$ tsgo --noEmit
drizzle.config.ts(1,10): error TS2305: Module '"./sst-env.d.ts"' has no exported member 'Resource'.
drizzle.config.ts(1,26): error TS2846: A declaration file cannot be imported without 'import type'. Did you mean to import an implementation file './sst-env.js' instead?
Found 1 matches
/home/jm/data/code/opencode/packages/console/resource/package.json:
Line 3: "name": "@opencode-ai/console-resource",No files found<file>
00001| {
00002| "$schema": "https://json.schemastore.org/package.json",
00003| "name": "@opencode-ai/console-resource",
00004| "license": "MIT",
00005| "dependencies": {
00006| "@cloudflare/workers-types": "catalog:"
00007| },
00008| "exports": {
00009| ".": {
00010| "production": {
00011| "import": "./resource.cloudflare.ts"
00012| },
00013| "import": "./resource.node.ts"
00014| }
00015| },
00016| "devDependencies": {
00017| "@cloudflare/workers-types": "catalog:",
00018| "@tsconfig/node22": "22.0.2",
00019| "@types/node": "catalog:",
00020| "cloudflare": "5.2.0"
00021| }
00022| }
00023|
(End of file - total 23 lines)
</file>/home/jm/data/code/opencode/packages/console/resource/package.json
/home/jm/data/code/opencode/packages/console/resource/bun.lock
/home/jm/data/code/opencode/packages/console/resource/tsconfig.json
/home/jm/data/code/opencode/packages/console/resource/sst-env.d.ts
/home/jm/data/code/opencode/packages/console/resource/resource.node.ts
/home/jm/data/code/opencode/packages/console/resource/resource.cloudflare.ts<file>
00001| import type { KVNamespaceListOptions, KVNamespaceListResult, KVNamespacePutOptions } from "@cloudflare/workers-types"
00002| import Cloudflare from "cloudflare"
00003|
00004| export const waitUntil = async (promise: Promise<any>) => {
00005| await promise
00006| }
00007|
00008| export const Resource = {
00009| ADMIN_SECRET: { value: "" },
00010| AUTH_API_URL: { value: "" },
00011| AWS_SES_ACCESS_KEY_ID: { value: "" },
00012| AWS_SES_SECRET_ACCESS_KEY: { value: "" },
00013| CLOUDFLARE_API_TOKEN: { value: "" },
00014| CLOUDFLARE_DEFAULT_ACCOUNT_ID: { value: "" },
00015| Console: { url: "" },
00016| Database: { host: "", username: "", password: "", database: "", port: 3309 },
00017| EMAILOCTOPUS_API_KEY: { value: "" },
00018| GITHUB_APP_ID: { value: "" },
00019| GITHUB_APP_PRIVATE_KEY: { value: "" },
00020| GITHUB_CLIENT_ID_CONSOLE: { value: "" },
00021| GITHUB_CLIENT_SECRET_CONSOLE: { value: "" },
00022| GOOGLE_CLIENT_ID: { value: "" },
00023| HONEYCOMB_API_KEY: { value: "" },
00024| R2AccessKey: { value: "" },
00025| R2SecretKey: { value: "" },
00026| STRIPE_SECRET_KEY: { value: "" },
00027| STRIPE_WEBHOOK_SECRET: { value: "" },
00028| Teams: { url: "" },
00029| Web: { url: "" },
00030| WebApp: { url: "" },
00031| ZEN_MODELS1: { value: "" },
00032| ZEN_MODELS2: { value: "" },
00033| ZEN_MODELS3: { value: "" },
00034| ZEN_MODELS4: { value: "" },
00035| ZEN_MODELS5: { value: "" },
00036| ZEN_MODELS6: { value: "" },
00037| App: { stage: "" },
00038| } as any
00039|
(End of file - total 39 lines)
</file><file>
00001| import { env } from "cloudflare:workers"
00002| export { waitUntil } from "cloudflare:workers"
00003|
00004| export const Resource = new Proxy(
00005| {},
00006| {
00007| get(_target, prop: string) {
00008| if (prop in env) {
00009| // @ts-expect-error
00010| const value = env[prop]
00011| return typeof value === "string" ? JSON.parse(value) : value
00012| } else if (prop === "App") {
00013| // @ts-expect-error
00014| return JSON.parse(env.SST_RESOURCE_App)
00015| }
00016| throw new Error(`"${prop}" is not linked in your sst.config.ts (cloudflare)`)
00017| },
00018| },
00019| ) as Record<string, any>
00020|
(End of file - total 20 lines)
</file><file>
00001| /* This file is auto-generated by SST. Do not edit. */
00002| /* tslint:disable */
00003| /* eslint-disable */
00004| /* deno-fmt-ignore-file */
00005|
00006| import "sst"
00007| declare module "sst" {
00008| export interface Resource {
00009| "ADMIN_SECRET": {
00010| "type": "sst.sst.Secret"
00011| "value": string
00012| }
00013| "AUTH_API_URL": {
00014| "type": "sst.sst.Linkable"
00015| "value": string
00016| }
00017| "AWS_SES_ACCESS_KEY_ID": {
00018| "type": "sst.sst.Secret"
00019| "value": string
00020| }
00021| "AWS_SES_SECRET_ACCESS_KEY": {
00022| "type": "sst.sst.Secret"
00023| "value": string
00024| }
00025| "CLOUDFLARE_API_TOKEN": {
00026| "type": "sst.sst.Secret"
00027| "value": string
00028| }
00029| "CLOUDFLARE_DEFAULT_ACCOUNT_ID": {
00030| "type": "sst.sst.Secret"
00031| "value": string
00032| }
00033| "Console": {
00034| "type": "sst.cloudflare.SolidStart"
00035| "url": string
00036| }
00037| "DISCORD_SUPPORT_BOT_TOKEN": {
00038| "type": "sst.sst.Secret"
00039| "value": string
00040| }
00041| "DISCORD_SUPPORT_CHANNEL_ID": {
00042| "type": "sst.sst.Secret"
00043| "value": string
00044| }
00045| "Database": {
00046| "database": string
00047| "host": string
00048| "password": string
00049| "port": number
00050| "type": "sst.sst.Linkable"
00051| "username": string
00052| }
00053| "EMAILOCTOPUS_API_KEY": {
00054| "type": "sst.sst.Secret"
00055| "value": string
00056| }
00057| "FEISHU_APP_ID": {
00058| "type": "sst.sst.Secret"
00059| "value": string
00060| }
00061| "FEISHU_APP_SECRET": {
00062| "type": "sst.sst.Secret"
00063| "value": string
00064| }
00065| "GITHUB_APP_ID": {
00066| "type": "sst.sst.Secret"
00067| "value": string
00068| }
0006...No files foundYou’ve got two separate typing issues mixed together.
packages/console/core/drizzle.config.ts:1 is importing from a declaration file (.d.ts) as if it exported a runtime value.packages/console/core/sst-env.d.ts is declaration-only, and (despite the name) should not be used as a runtime import source.Use this fix:
// packages/console/core/drizzle.config.ts
import { Resource } from "@opencode-ai/console-resource"
import { defineConfig } from "drizzle-kit"
Why this works:
@opencode-ai/console-resource actually exports a Resource value (runtime + typed surface)../sst-env.d.ts only augments types (declare module "sst"), so TS throws:Resource.d.ts without import typeAlso check packages/console/core/sst-env.d.ts:
cloudflare.Service not found, switch those to cloudflare.Fetcher (service bindings are typically Fetcher in current workers types).If you want, once write mode is enabled I can apply the exact patch and run bun run typecheck in packages/console/core to confirm it’s clean.
There is a bunch of typing errors in packages/session project, what do you think of the types defined manually in src/types.ts of that project?
No files foundNo files found/home/jm/data/code/opencode/packages/opencode/test/session/session.test.ts
/home/jm/data/code/opencode/packages/opencode/test/session/revert-compact.test.ts
/home/jm/data/code/opencode/packages/opencode/test/session/retry.test.ts
/home/jm/data/code/opencode/packages/opencode/test/session/prompt-variant.test.ts
/home/jm/data/code/opencode/packages/opencode/test/session/prompt-special-chars.test.ts
/home/jm/data/code/opencode/packages/opencode/test/session/prompt-missing-file.test.ts
/home/jm/data/code/opencode/packages/opencode/test/session/message-v2.test.ts
/home/jm/data/code/opencode/packages/opencode/test/session/llm.test.ts
/home/jm/data/code/opencode/packages/opencode/test/session/instruction.test.ts
/home/jm/data/code/opencode/packages/opencode/test/session/compaction.test.ts
/home/jm/data/code/opencode/packages/opencode/src/session/todo.ts
/home/jm/data/code/opencode/packages/opencode/src/session/system.ts
/home/jm/data/code/opencode/packages/opencode/src/session/summary.ts
/home/jm/data/code/opencode/packages/opencode/src/session/status.ts
/home/jm/data/code/opencode/packages/opencode/src/session/revert.ts
/home/jm/data/code/opencode/packages/opencode/src/session/retry.ts
/home/jm/data/code/opencode/packages/opencode/src/session/prompt.ts
/home/jm/data/code/opencode/packages/opencode/src/session/processor.ts
/home/jm/data/code/opencode/packages/opencode/src/session/message.ts
/home/jm/data/code/opencode/packages/opencode/src/session/message-v2.ts
/home/jm/data/code/opencode/packages/opencode/src/session/llm.ts
/home/jm/data/code/opencode/packages/opencode/src/session/instruction.ts
/home/jm/data/code/opencode/packages/opencode/src/session/index.ts
/home/jm/data/code/opencode/packages/opencode/src/session/prompt/trinity.txt
/home/jm/data/code/opencode/packages/opencode/src/session/prompt/qwen.txt
/home/jm/data/code/opencode/packages/opencode/src/session/prompt/plan.txt
/home/jm/data/code/opencode/packages/opencode/src/session/prompt/plan-reminder-anthropic.tx.../home/jm/data/code/opencode/packages/sessions/src/types.ts/home/jm/data/code/opencode/packages/sessions/src/types.ts<file>
00001| /**
00002| * SDK Types for OpenCode Agent Sessions
00003| * Import core types from @opencode-ai/sdk/v2
00004| */
00005| import type { Session, Message, Part, FileDiff, Model } from "@opencode-ai/sdk/v2"
00006|
00007| // Re-export SDK types for convenience
00008| export type { Session, Message, Part, FileDiff, Model }
00009|
00010| /**
00011| * Sync data discriminated union type
00012| * Represents the different types of data that can be synchronized
00013| */
00014| export type SyncData =
00015| | { type: "session"; data: Session }
00016| | { type: "message"; data: Message }
00017| | { type: "part"; data: Part }
00018| | { type: "session_diff"; data: FileDiff[] }
00019| | { type: "model"; data: Model[] }
00020|
00021| /**
00022| * Complete agent session structure
00023| * This represents a fully reconstructed session from sync data
00024| */
00025| export type SessionMetadata = {
00026| createdAt: number
00027| lastUpdated: number
00028| syncCount: number
00029| secret: string
00030| sessionID: string
00031| }
00032|
00033| export type AgentSession = {
00034| session: Session
00035| messages: Message[]
00036| parts: Part[]
00037| diffs: FileDiff[]
00038| models: Model[]
00039| metadata: SessionMetadata
00040| }
00041|
00042| /**
00043| * Share credentials
00044| */
00045| export type SyncInfo = {
00046| id: string
00047| url: string
00048| secret: string
00049| }
00050|
00051| export type ShareCredentials = SyncInfo & {
00052| sessionID: string
00053| createdAt: number
00054| }
00055|
00056| export type SecretNS = string
00057|
(End of file - total 57 lines)
</file><file>
00001| {
00002| "$schema": "https://json.schemastore.org/package.json",
00003| "name": "@opencode-ai/sessions",
00004| "version": "1.0.207",
00005| "private": true,
00006| "type": "module",
00007| "module": "index.ts",
00008| "scripts": {
00009| "generate-types": "wrangler types",
00010| "typecheck": "wrangler types && tsc --noEmit",
00011| "test": "bun test src/**/*.test.ts",
00012| "test:api": "bun test src/api.test.ts",
00013| "test:storage": "bun test src/storage.test.ts",
00014| "dev": "wrangler dev --env dev",
00015| "deploy": "wrangler deploy"
00016| },
00017| "devDependencies": {
00018| "@tsconfig/bun": "1.0.9",
00019| "@types/bun": "latest",
00020| "@types/node": "22.13.9",
00021| "typescript": "5.9.3",
00022| "wrangler": "4.60.0"
00023| },
00024| "dependencies": {
00025| "@hono/zod-validator": "catalog:",
00026| "@opencode-ai/sdk": "workspace:*",
00027| "hono": "catalog:",
00028| "uuid": "13.0.0",
00029| "zod": "catalog:"
00030| }
00031| }
00032|
(End of file - total 32 lines)
</file>/home/jm/data/code/opencode/packages/sessions/tsconfig.json<file>
00001| {
00002| "compilerOptions": {
00003| // Environment setup & latest features
00004| "lib": ["ESNext"],
00005| "target": "ESNext",
00006| "module": "esnext",
00007| "moduleResolution": "bundler",
00008|
00009| "allowJs": true,
00010|
00011| // Type enforcement
00012| "types": ["node", "./worker-configuration"],
00013|
00014| // Bundler mode
00015| "allowImportingTsExtensions": true,
00016| "verbatimModuleSyntax": true,
00017| "noEmit": true,
00018|
00019| // Best practices
00020| "strict": true,
00021| "skipLibCheck": true,
00022| "noFallthroughCasesInSwitch": true,
00023| "noUncheckedIndexedAccess": true,
00024| "noImplicitOverride": true,
00025|
00026| // Some stricter flags (disabled by default)
00027| "noUnusedLocals": false,
00028| "noUnusedParameters": false,
00029| "noPropertyAccessFromIndexSignature": false,
00030|
00031| // Extra added by claude attempt
00032| "jsxImportSource": "hono/jsx",
00033| "esModuleInterop": true,
00034| "allowSyntheticDefaultImports": true,
00035| "forceConsistentCasingInFileNames": true,
00036| "resolveJsonModule": true,
00037| "isolatedModules": true
00038| },
00039|
00040| "include": ["src/**/*.ts", "worker-configuration.d.ts"],
00041| "exclude": ["node_modules"]
00042| }
00043|
(End of file - total 43 lines)
</file>/home/jm/data/code/opencode/packages/sessions/src/types.ts
/home/jm/data/code/opencode/packages/sessions/src/test-utils.ts
/home/jm/data/code/opencode/packages/sessions/src/index.test.ts
/home/jm/data/code/opencode/packages/sessions/src/storage.ts
/home/jm/data/code/opencode/packages/sessions/src/storage.test.ts
/home/jm/data/code/opencode/packages/sessions/src/index.ts
/home/jm/data/code/opencode/packages/sessions/src/api.test.tsFound 21 matches
/home/jm/data/code/opencode/packages/sessions/src/types.ts:
Line 5: import type { Session, Message, Part, FileDiff, Model } from "@opencode-ai/sdk/v2"
Line 8: export type { Session, Message, Part, FileDiff, Model }
Line 14: export type SyncData =
Line 25: export type SessionMetadata = {
Line 33: export type AgentSession = {
Line 39: metadata: SessionMetadata
Line 51: export type ShareCredentials = SyncInfo & {
Line 56: export type SecretNS = string
/home/jm/data/code/opencode/packages/sessions/src/test-utils.ts:
Line 7: import type { Session, Message, Part, FileDiff, Model } from "../src/types"
/home/jm/data/code/opencode/packages/sessions/src/index.test.ts:
Line 3: import type { AgentSession, SyncInfo } from "./types"
Line 149: const shareSession = await parseJson<AgentSession>(shareResponse)
/home/jm/data/code/opencode/packages/sessions/src/index.ts:
Line 6: import type { SyncInfo, ShareCredentials, SyncData, AgentSession, Session } from "./types"
Line 30: sessions: StorageAdapter<AgentSession>
Line 34: // credentials: createStorageAdapter<ShareCredentials>(bucket),
Line 35: sessions: createStorageAdapter<AgentSession>(bucket),
Line 78: const credentialsData: ShareCredentials = {
Line 85: const initialSession: AgentSession = {
Line 142: const nextSession: AgentSession = {
/home/jm/data/code/opencode/packages/sessions/src/api.test.ts:
Line 3: import type { AgentSession, SyncInfo } from "./types"
Line 136: return { response, data: await parseJson<AgentSession>(response) }
Line 467: test("returns AgentSession with all fields", async () => {$ wrangler types && tsc --noEmit
⛅️ wrangler 4.60.0 (update available 4.63.0)
─────────────────────────────────────────────
Generating project types...
declare namespace Cloudflare {
interface GlobalProps {
mainModule: typeof import("./src/index");
}
interface Env {
SESSIONS_STORE: R2Bucket;
SESSIONS_SHARED_SECRET: string;
API_DOMAIN: string;
}
}
interface Env extends Cloudflare.Env {}
type StringifyValues<EnvType extends Record<string, unknown>> = {
[Binding in keyof EnvType]: EnvType[Binding] extends string ? EnvType[Binding] : string;
};
declare namespace NodeJS {
interface ProcessEnv extends StringifyValues<Pick<Cloudflare.Env, "SESSIONS_SHARED_SECRET" | "API_DOMAIN">> {}
}
Generating runtime types...
Runtime types generated.
✨ Types written to worker-configuration.d.ts
📖 Read about runtime types
https://developers.cloudflare.com/workers/languages/typescript/#generate-types
📣 Remember to rerun 'wrangler types' after you change your wrangler.jsonc file.
src/api.test.ts(1,52): error TS2307: Cannot find module 'bun:test' or its corresponding type declarations.
src/api.test.ts(254,12): error TS2532: Object is possibly 'undefined'.
src/api.test.ts(255,12): error TS2532: Object is possibly 'undefined'.
src/api.test.ts(275,12): error TS2532: Object is possibly 'undefined'.
src/api.test.ts(314,12): error TS2532: Object is possibly 'undefined'.
src/api.test.ts(314,30): error TS2339: Property 'text' does not exist on type 'Part'.
Property 'text' does not exist on type 'SubtaskPart'.
src/api.test.ts(368,12): error TS2532: Object is possibly 'undefined'.
src/api.test.ts(547,12): error TS2532: Object is possibly 'undefined'.
src/api.test.ts(548,19): error TS2532: Object is possibly 'undefined'.
src/index.test.ts(1,40): error TS2307: Cannot find module 'bun:test' or its corresponding type declarations.
src/storage.test.ts(6,50): error TS2307: Cannot find module 'bun:test' or its corresponding type declarations.
src/storage.test.ts(60,12): error TS25...No files foundFound 100 matches
/home/jm/data/code/opencode/packages/sdk/js/src/v2/gen/types.gen.ts:
Line 93: export type FileDiff = {
Line 467: export type Part =
Line 527: export type SessionStatus =
Line 541: export type EventSessionStatus = {
Line 549: export type EventSessionIdle = {
Line 627: export type EventSessionCompacted = {
Line 713: export type EventTuiSessionSelect = {
Line 758: export type Session = {
Line 790: export type EventSessionCreated = {
Line 797: export type EventSessionUpdated = {
Line 804: export type EventSessionDeleted = {
Line 811: export type EventSessionDiff = {
Line 819: export type EventSessionError = {
Line 1868: export type Model = {
Line 2832: export type SessionListData = {
Line 2860: export type SessionListResponses = {
Line 2867: export type SessionListResponse = SessionListResponses[keyof SessionListResponses]
Line 2869: export type SessionCreateData = {
Line 2882: export type SessionCreateErrors = {
Line 2889: export type SessionCreateError = SessionCreateErrors[keyof SessionCreateErrors]
Line 2891: export type SessionCreateResponses = {
Line 2898: export type SessionCreateResponse = SessionCreateResponses[keyof SessionCreateResponses]
Line 2900: export type SessionStatusData = {
Line 2909: export type SessionStatusErrors = {
Line 2916: export type SessionStatusError = SessionStatusErrors[keyof SessionStatusErrors]
Line 2918: export type SessionStatusResponses = {
Line 2927: export type SessionStatusResponse = SessionStatusResponses[keyof SessionStatusResponses]
Line 2929: export type SessionDeleteData = {
Line 2940: export type SessionDeleteErrors = {
Line 2951: export type SessionDeleteError = SessionDeleteErrors[keyof SessionDeleteErrors]
Line 2953: export type SessionDeleteResponses = {
Line 2960: export type SessionDeleteResponse = SessionDeleteResponses[keyof SessionDeleteResponses]
Line 2962: export type SessionGetData = {
Line 2973: export type SessionGetErrors = ...<file>
00001| import { Hono } from "hono"
00002| import { cors } from "hono/cors"
00003| import { zValidator } from "@hono/zod-validator"
00004| import { z } from "zod"
00005| import { v5 as uuidv5 } from "uuid"
00006| import type { SyncInfo, ShareCredentials, SyncData, AgentSession, Session } from "./types"
00007| import { createStorageAdapter, type StorageAdapter } from "./storage"
00008|
00009| /**
00010| * Environment bindings for Cloudflare Worker
00011| */
00012| type Env = {
00013| SESSIONS_STORE: R2Bucket
00014| SESSIONS_SHARED_SECRET: string
00015| API_DOMAIN: string
00016| }
00017|
00018| /**
00019| * Main Hono application
00020| */
00021| const app = new Hono<{ Bindings: Env }>()
00022|
00023| // Enable CORS for all routes
00024| app.use("/*", cors())
00025|
00026| /**
00027| * Create a storage adapter from the R2 bucket binding
00028| */
00029| function getStorageAdapter(c: any): {
00030| sessions: StorageAdapter<AgentSession>
00031| } {
00032| const bucket = c.env.SESSIONS_STORE
00033| return {
00034| // credentials: createStorageAdapter<ShareCredentials>(bucket),
00035| sessions: createStorageAdapter<AgentSession>(bucket),
00036| }
00037| }
00038|
00039| /**
00040| * Health check endpoint
00041| */
00042| app.get("/", (c) => {
00043| return c.json({
00044| service: "opencode-sessions-api",
00045| version: "1.0.0",
00046| status: "healthy",
00047| })
00048| })
00049|
00050| /**
00051| * Create a new share
00052| * POST /api/share
00053| */
00054| app.post(
00055| "/api/share",
00056| zValidator(
00057| "json",
00058| z.object({
00059| sessionID: z.string(),
00060| }),
00061| ),
00062| async (c) => {
00063| const { sessionID } = c.req.valid("json")
00064| const { sessions } = getStorageAdapter(c)
00065|
00066| const shareID = sessionID.slice(-8)
00067|
00068| const shareSecret = uuidv5(sessionID, c.env.SESSIONS_SHARED_SECRET)
00069| const now = Date.now...<file>
00731| type: "mcp.browser.open.failed"
00732| properties: {
00733| mcpName: string
00734| url: string
00735| }
00736| }
00737|
00738| export type EventCommandExecuted = {
00739| type: "command.executed"
00740| properties: {
00741| name: string
00742| sessionID: string
00743| arguments: string
00744| messageID: string
00745| }
00746| }
00747|
00748| export type PermissionAction = "allow" | "deny" | "ask"
00749|
00750| export type PermissionRule = {
00751| permission: string
00752| pattern: string
00753| action: PermissionAction
00754| }
00755|
00756| export type PermissionRuleset = Array<PermissionRule>
00757|
00758| export type Session = {
00759| id: string
00760| slug: string
00761| projectID: string
00762| directory: string
00763| parentID?: string
00764| summary?: {
00765| additions: number
00766| deletions: number
00767| files: number
00768| diffs?: Array<FileDiff>
00769| }
00770| share?: {
00771| url: string
00772| }
00773| title: string
00774| version: string
00775| time: {
00776| created: number
00777| updated: number
00778| compacting?: number
00779| archived?: number
00780| }
00781| permission?: PermissionRuleset
00782| revert?: {
00783| messageID: string
00784| partID?: string
00785| snapshot?: string
00786| diff?: string
00787| }
00788| }
00789|
00790| export type EventSessionCreated = {
00791| type: "session.created"
00792| properties: {
00793| info: Session
00794| }
00795| }
00796|
00797| export type EventSessionUpdated = {
00798| type: "session.updated"
00799| properties: {
00800| info: Session
00801| }
00802| }
00803|
00804| export type EventSessionDeleted = {
00805| type: "session.deleted"
00806| properties: {
00807| info: Session
00808| }
00809| }
00810|
00811| export type EventSessionDiff = {
00812| type: "session.diff"
00813| properties: {
00814| sessionID: string
00815|...<file>
00431| files: Array<string>
00432| }
00433|
00434| export type AgentPart = {
00435| id: string
00436| sessionID: string
00437| messageID: string
00438| type: "agent"
00439| name: string
00440| source?: {
00441| value: string
00442| start: number
00443| end: number
00444| }
00445| }
00446|
00447| export type RetryPart = {
00448| id: string
00449| sessionID: string
00450| messageID: string
00451| type: "retry"
00452| attempt: number
00453| error: ApiError
00454| time: {
00455| created: number
00456| }
00457| }
00458|
00459| export type CompactionPart = {
00460| id: string
00461| sessionID: string
00462| messageID: string
00463| type: "compaction"
00464| auto: boolean
00465| }
00466|
00467| export type Part =
00468| | TextPart
00469| | SubtaskPart
00470| | ReasoningPart
00471| | FilePart
00472| | ToolPart
00473| | StepStartPart
00474| | StepFinishPart
00475| | SnapshotPart
00476| | PatchPart
00477| | AgentPart
00478| | RetryPart
00479| | CompactionPart
00480|
00481| export type EventMessagePartUpdated = {
00482| type: "message.part.updated"
00483| properties: {
00484| part: Part
00485| delta?: string
00486| }
00487| }
00488|
00489| export type EventMessagePartRemoved = {
00490| type: "message.part.removed"
00491| properties: {
00492| sessionID: string
00493| messageID: string
00494| partID: string
00495| }
00496| }
00497|
00498| export type PermissionRequest = {
00499| id: string
00500| sessionID: string
00501| permission: string
00502| patterns: Array<string>
00503| metadata: {
00504| [key: string]: unknown
00505| }
00506| always: Array<string>
00507| tool?: {
00508| messageID: string
00509| callID: string
00510| }
00511| }
00512|
00513| export type EventPermissionAsked = {
00514| type: "permission.asked"
00515| properties: PermissionRequest
00516| }
00517|
00518| export type EventPermissionReplied = {
0051...<file>
00001| /**
00002| * Auto-generated type validation utilities for sessions API tests
00003| * Generated from SDK types - DO NOT EDIT MANUALLY
00004| * Keep this file in sync when SDK types change
00005| */
00006|
00007| import type { Session, Message, Part, FileDiff, Model } from "../src/types"
00008| import { expect } from "bun:test"
00009|
00010| /**
00011| * Type validation utility that automatically updates with SDK changes
00012| */
00013| export function validateTypeStructure(obj: any, typeName: string, expectedProperties: string[]) {
00014| expectedProperties.forEach((prop) => {
00015| expect(obj).toHaveProperty(prop)
00016| })
00017| return true
00018| }
00019|
00020| /**
00021| * Automatically generated property lists from SDK types
00022| * These will update when SDK types change
00023| */
00024|
00025| export const SESSION_PROPERTIES = ["id", "projectID", "directory", "title", "version", "time"]
00026| export const MESSAGE_PROPERTIES = ["id", "sessionID", "role", "time"]
00027| export const PART_PROPERTIES = ["id", "sessionID", "messageID", "type", "text"]
00028| export const MODEL_PROPERTIES = ["id", "providerID", "api", "name", "capabilities", "cost", "limit", "status"]
00029| export const FILEDIFF_PROPERTIES = ["file", "before", "after", "additions", "deletions"]
00030|
00031| /**
00032| * Validate that an object matches the structure of a specific SDK type
00033| */
00034|
00035| export function validateSessionStructure(obj: any) {
00036| return validateTypeStructure(obj, "Session", SESSION_PROPERTIES)
00037| }
00038| export function validateMessageStructure(obj: any) {
00039| return validateTypeStructure(obj, "Message", MESSAGE_PROPERTIES)
00040| }
00041| export function validatePartStructure(obj: any) {
00042| return validateTypeStructure(obj, "Part", PART_PROPERTIES)
00043| }
00044| export function validateModelStructure(obj: any) {
00045| return validateTypeStructure(obj, "Model", MODEL_PROPERTIES)
00046| }...<file>
00241|
00242| await syncShare(share.id, env, {
00243| secret: share.secret,
00244| data: [{ type: "message", data: msg1 }],
00245| })
00246|
00247| await syncShare(share.id, env, {
00248| secret: share.secret,
00249| data: [{ type: "message", data: msg2 }],
00250| })
00251|
00252| const { data: session } = await getShare(share.id, env)
00253| expect(session?.messages).toHaveLength(2)
00254| expect(session?.messages[0].id).toBe("msg-1")
00255| expect(session?.messages[1].id).toBe("msg-2")
00256| })
00257|
00258| test("updates existing message with same ID", async () => {
00259| const { data: share } = await createShare("msg-update", env)
00260| const msg = createTestMessage({ id: "msg-1", sessionID: "msg-update", role: "user" })
00261|
00262| await syncShare(share.id, env, {
00263| secret: share.secret,
00264| data: [{ type: "message", data: msg }],
00265| })
00266|
00267| const updatedMsg = { ...msg, role: "assistant" }
00268| await syncShare(share.id, env, {
00269| secret: share.secret,
00270| data: [{ type: "message", data: updatedMsg }],
00271| })
00272|
00273| const { data: session } = await getShare(share.id, env)
00274| expect(session?.messages).toHaveLength(1)
00275| expect(session?.messages[0].role).toBe("assistant")
00276| })
00277|
00278| test("appends new parts", async () => {
00279| const { data: share } = await createShare("part-test", env)
00280| const part1 = createTestPart({ id: "part-1", sessionID: "part-test", messageID: "msg-1" })
00281| const part2 = createTestPart({ id: "part-2", sessionID: "part-test", messageID: "msg-1" })
00282|
00283| await syncShare(share.id, env, {
00284| secret: share.secret,
00285| data: [{ type: "part", data: part1 }],
00286| })
00287|
00288| await syncShare(share.id, env, {
00289| secret: share.secret,
00290| data: [{ type: "part...<file>
00351| test("updates existing model with same ID", async () => {
00352| const { data: share } = await createShare("model-update", env)
00353| const model = createTestModel({ id: "gpt-4", providerID: "openai", name: "GPT-4" })
00354|
00355| await syncShare(share.id, env, {
00356| secret: share.secret,
00357| data: [{ type: "model", data: [model] }],
00358| })
00359|
00360| const updatedModel = { ...model, name: "GPT-4 Turbo" }
00361| await syncShare(share.id, env, {
00362| secret: share.secret,
00363| data: [{ type: "model", data: [updatedModel] }],
00364| })
00365|
00366| const { data: session } = await getShare(share.id, env)
00367| expect(session?.models).toHaveLength(1)
00368| expect(session?.models[0].name).toBe("GPT-4 Turbo")
00369| })
00370|
00371| test("increments syncCount on each sync", async () => {
00372| const { data: share } = await createShare("count-test", env)
00373|
00374| const r1 = await syncShare(share.id, env, {
00375| secret: share.secret,
00376| data: [{ type: "session", data: createTestSession() }],
00377| })
00378| expect(r1.data.syncCount).toBe(1)
00379|
00380| const r2 = await syncShare(share.id, env, {
00381| secret: share.secret,
00382| data: [{ type: "session", data: createTestSession() }],
00383| })
00384| expect(r2.data.syncCount).toBe(2)
00385|
00386| const r3 = await syncShare(share.id, env, {
00387| secret: share.secret,
00388| data: [{ type: "session", data: createTestSession() }],
00389| })
00390| expect(r3.data.syncCount).toBe(3)
(File has more lines. Use 'offset' parameter to read beyond line 390)
</file><file>
00531|
00532| expect(data.count).toBe(2)
00533| const ids = data.sessions.map((s) => s.sessionID)
00534| expect(ids).toContain("session-list-a")
00535| expect(ids).toContain("session-list-b")
00536| })
00537|
00538| test("includes sessionID and createdAt for each", async () => {
00539| const { data: share } = await createShare("session-fields", env)
00540| await syncShare(share.id, env, {
00541| secret: share.secret,
00542| data: [{ type: "session", data: createTestSession({ id: "session-fields" }) }],
00543| })
00544|
00545| const { data } = await listSessions(env)
00546|
00547| expect(data.sessions[0].sessionID).toBe("session-fields")
00548| expect(typeof data.sessions[0].createdAt).toBe("number")
00549| })
00550| })
00551|
00552| describe("GET /api/share/:id/metadata", () => {
00553| let env: TestEnv
00554|
00555| beforeEach(() => {
00556| env = createEnv()
00557| })
00558|
00559| test("returns summary without full data", async () => {
00560| const { data: share } = await createShare("meta-test", env)
00561| const session = createTestSession({ id: "meta-test", title: "Metadata Test" })
00562| const msg = createTestMessage({ id: "msg-1", sessionID: "meta-test" })
00563| const part = createTestPart({ id: "part-1", sessionID: "meta-test", messageID: "msg-1" })
00564| const diff = createTestFileDiff({ file: "test.ts" })
00565| const model = createTestModel({ id: "gpt-4" })
00566|
00567| await syncShare(share.id, env, {
00568| secret: share.secret,
00569| data: [
00570| { type: "session", data: session },
(File has more lines. Use 'offset' parameter to read beyond line 570)
</file><file>
00046| expect(await adapter.exists("test-key")).toBeTrue()
00047| })
00048|
00049| it("should list values with prefix", async () => {
00050| const data1 = { id: "test1" }
00051| const data2 = { id: "test2" }
00052| const data3 = { id: "other" }
00053|
00054| await adapter.put("credentials/test1", data1)
00055| await adapter.put("credentials/test2", data2)
00056| await adapter.put("sessions/other", data3)
00057|
00058| const result = await adapter.list({ prefix: "credentials/" })
00059| expect(result.length).toBe(2)
00060| expect(result[0].key).toContain("credentials/")
00061| expect(result[1].key).toContain("credentials/")
00062| })
00063|
00064| it("should clear all data", async () => {
00065| const testData = { id: "test" }
00066|
00067| await adapter.put("test-key", testData)
00068| expect(await adapter.get("test-key")).not.toBeNull()
00069|
00070| adapter.clear()
00071| expect(await adapter.get("test-key")).toBeNull()
00072| })
00073| })
00074|
00075| describe("Storage Utilities", () => {
(File has more lines. Use 'offset' parameter to read beyond line 75)
</file><file>
00002| import app from "./index"
00003| import type { AgentSession, SyncInfo } from "./types"
00004| import {
00005| createTestFileDiff,
00006| createTestMessage,
00007| createTestModel,
00008| createTestPart,
00009| createTestSession,
00010| SESSION_PROPERTIES,
00011| validateTypeStructure,
00012| } from "./test-utils"
00013|
00014| const sharedSecret = "6ba7b810-9dad-11d1-80b4-00c04fd430c8"
00015|
00016| const createMockR2Bucket = () => {
00017| const storage = new Map<string, string>()
00018|
00019| const mockBucket: any = {
00020| put: async (key: string, value: string | ArrayBuffer) => {
00021| let content = ""
00022| if (typeof value === "string") {
00023| content = value
00024| }
00025| if (value instanceof ArrayBuffer) {
00026| content = new TextDecoder().decode(value)
00027| }
00028|
00029| storage.set(key, content)
00030| },
00031| get: async (key: string) => {
00032| const data = storage.get(key)
00033| if (!data) return null
00034|
00035| return {
00036| async text() {
00037| return data
00038| },
00039| async arrayBuffer() {
00040| return new TextEncoder().encode(data).buffer
00041| },
(File has more lines. Use 'offset' parameter to read beyond line 41)
</file><file>
00341|
00342| export type ToolStateCompleted = {
00343| status: "completed"
00344| input: {
00345| [key: string]: unknown
00346| }
00347| output: string
00348| title: string
00349| metadata: {
00350| [key: string]: unknown
00351| }
00352| time: {
00353| start: number
00354| end: number
00355| compacted?: number
00356| }
00357| attachments?: Array<FilePart>
00358| }
00359|
00360| export type ToolStateError = {
00361| status: "error"
00362| input: {
00363| [key: string]: unknown
00364| }
00365| error: string
00366| metadata?: {
00367| [key: string]: unknown
00368| }
00369| time: {
00370| start: number
00371| end: number
00372| }
00373| }
00374|
00375| export type ToolState = ToolStatePending | ToolStateRunning | ToolStateCompleted | ToolStateError
00376|
00377| export type ToolPart = {
00378| id: string
00379| sessionID: string
00380| messageID: string
00381| type: "tool"
00382| callID: string
00383| tool: string
00384| state: ToolState
00385| metadata?: {
00386| [key: string]: unknown
00387| }
00388| }
00389|
00390| export type StepStartPart = {
00391| id: string
00392| sessionID: string
00393| messageID: string
00394| type: "step-start"
00395| snapshot?: string
00396| }
00397|
00398| export type StepFinishPart = {
00399| id: string
00400| sessionID: string
00401| messageID: string
00402| type: "step-finish"
00403| reason: string
00404| snapshot?: string
00405| cost: number
00406| tokens: {
00407| input: number
00408| output: number
00409| reasoning: number
00410| cache: {
00411| read: number
00412| write: number
00413| }
00414| }
00415| }
00416|
00417| export type SnapshotPart = {
00418| id: string
00419| sessionID: string
00420| messageID: string
00421| type: "snapshot"
00422| snapshot: string
00423| }
00424|
00425| export type PatchPart = {
00426| id: string
00427| sessionID: string
0...<file>
01831| export type BadRequestError = {
01832| data: unknown
01833| errors: Array<{
01834| [key: string]: unknown
01835| }>
01836| success: false
01837| }
01838|
01839| export type OAuth = {
01840| type: "oauth"
01841| refresh: string
01842| access: string
01843| expires: number
01844| accountId?: string
01845| enterpriseUrl?: string
01846| }
01847|
01848| export type ApiAuth = {
01849| type: "api"
01850| key: string
01851| }
01852|
01853| export type WellKnownAuth = {
01854| type: "wellknown"
01855| key: string
01856| token: string
01857| }
01858|
01859| export type Auth = OAuth | ApiAuth | WellKnownAuth
01860|
01861| export type NotFoundError = {
01862| name: "NotFoundError"
01863| data: {
01864| message: string
01865| }
01866| }
01867|
01868| export type Model = {
01869| id: string
01870| providerID: string
01871| api: {
01872| id: string
01873| url: string
01874| npm: string
01875| }
01876| name: string
01877| family?: string
01878| capabilities: {
01879| temperature: boolean
01880| reasoning: boolean
01881| attachment: boolean
01882| toolcall: boolean
01883| input: {
01884| text: boolean
01885| audio: boolean
01886| image: boolean
01887| video: boolean
01888| pdf: boolean
01889| }
01890| output: {
01891| text: boolean
01892| audio: boolean
01893| image: boolean
01894| video: boolean
01895| pdf: boolean
01896| }
01897| interleaved:
01898| | boolean
01899| | {
01900| field: "reasoning_content" | "reasoning_details"
(File has more lines. Use 'offset' parameter to read beyond line 1900)
</file>Good instinct to question that file. Looking at packages/sessions/src/types.ts, I’d say:
Session, Message, Part, FileDiff, Model) instead of redefining them.SyncData, AgentSession, SessionMetadata) drifting from runtime usage.Session now requires slug, but test fixtures don’t include it (packages/sessions/src/test-utils.ts:55).Part is a union, so part.text is unsafe without narrowing (packages/sessions/src/api.test.ts:314).AgentSession currently forces session: Session, but runtime initializes with {} as Session (packages/sessions/src/index.ts:86), which is a type escape hatch and hides real incompatibilities.SecretNS = string in packages/sessions/src/types.ts:56 is effectively unused/no-op and can be removed unless you plan to brand it.My take: the core strategy in packages/sessions/src/types.ts is good, but the package still has “manual shape drift” in tests and initialization code. The file is not the main problem; the unsafe fixture/init patterns are.
If you want, I can do a focused cleanup pass that:
bun:test typing config, Session/Part, {} as Session by making initialization type-safe.GO ahead and try those changes
Found 2 matches
/home/jm/data/code/opencode/packages/ui/tsconfig.json:
Line 19: "types": ["vite/client", "bun"]
/home/jm/data/code/opencode/packages/app/e2e/tsconfig.json:
Line 5: "types": ["node", "bun"]No files found<file>
00002| import app from "./index"
00003| import type { AgentSession, SyncInfo } from "./types"
00004| import { createTestFileDiff, createTestMessage, createTestModel, createTestPart, createTestSession } from "./test-utils"
00005|
00006| const SHARED_SECRET = "6ba7b810-9dad-11d1-80b4-00c04fd430c8"
00007|
00008| type TestEnv = {
00009| SESSIONS_STORE: R2Bucket
00010| SESSIONS_SHARED_SECRET: string
00011| API_DOMAIN: string
00012| }
00013|
00014| function createMockR2Bucket() {
00015| const storage = new Map<string, string>()
00016|
00017| return {
00018| put: async (key: string, value: string | ArrayBuffer | ReadableStream) => {
00019| let content = ""
00020| if (typeof value === "string") {
00021| content = value
00022| } else if (value instanceof ArrayBuffer) {
00023| content = new TextDecoder().decode(value)
00024| } else if (value instanceof ReadableStream) {
00025| const reader = value.getReader()
00026| const chunks: Uint8Array[] = []
00027| while (true) {
00028| const { done, value: chunk } = await reader.read()
00029| if (done) break
00030| chunks.push(chunk)
00031| }
00032| content = new TextDecoder().decode(Buffer.concat(chunks))
00033| }
00034| storage.set(key, content)
00035| },
00036| get: async (key: string) => {
00037| const data = storage.get(key)
00038| if (!data) return null
00039| return {
00040| async text() {
00041| return data
00042| },
00043| async arrayBuffer() {
00044| return new TextEncoder().encode(data).buffer
00045| },
00046| async json() {
00047| return JSON.parse(data)
00048| },
00049| }
00050| },
00051| delete: async (key: string) => {
00052| storage.delete(key)
00053| },
00054| list: async (options?: { prefix?: string }) => {
00055| const prefix = options?....<file>
00002| import app from "./index"
00003| import type { AgentSession, SyncInfo } from "./types"
00004| import {
00005| createTestFileDiff,
00006| createTestMessage,
00007| createTestModel,
00008| createTestPart,
00009| createTestSession,
00010| SESSION_PROPERTIES,
00011| validateTypeStructure,
00012| } from "./test-utils"
00013|
00014| const sharedSecret = "6ba7b810-9dad-11d1-80b4-00c04fd430c8"
00015|
00016| const createMockR2Bucket = () => {
00017| const storage = new Map<string, string>()
00018|
00019| const mockBucket: any = {
00020| put: async (key: string, value: string | ArrayBuffer) => {
00021| let content = ""
00022| if (typeof value === "string") {
00023| content = value
00024| }
00025| if (value instanceof ArrayBuffer) {
00026| content = new TextDecoder().decode(value)
00027| }
00028|
00029| storage.set(key, content)
00030| },
00031| get: async (key: string) => {
00032| const data = storage.get(key)
00033| if (!data) return null
00034|
00035| return {
00036| async text() {
00037| return data
00038| },
00039| async arrayBuffer() {
00040| return new TextEncoder().encode(data).buffer
00041| },
00042| }
00043| },
00044| delete: async (key: string) => {
00045| storage.delete(key)
00046| },
00047| list: async (options?: any) => {
00048| const prefix = options?.prefix || ""
00049| const objects = Array.from(storage.keys())
00050| .filter((key) => key.startsWith(prefix))
00051| .map((key) => ({
00052| key,
00053| version: "mock",
00054| size: storage.get(key)!.length,
00055| etag: `"mock-${key}"`,
00056| httpMetadata: { contentType: "application/json" },
00057| customMetadata: {},
00058| uploaded: new Date(),
00059| }))
00060| return { objects }
00061| },
00062| ...<file>
00002| * Storage Adapter Tests
00003| * Unit tests for the storage adapter implementation
00004| */
00005|
00006| import { describe, it, expect, beforeEach } from "bun:test"
00007| import { MockStorageAdapter, Storage } from "./storage"
00008|
00009| describe("MockStorageAdapter", () => {
00010| let adapter: MockStorageAdapter<any>
00011|
00012| beforeEach(() => {
00013| adapter = new MockStorageAdapter()
00014| })
00015|
00016| it("should put and get values", async () => {
00017| const testData = { id: "test", secret: "secret123", sessionID: "session123" }
00018|
00019| await adapter.put("test-key", testData)
00020| const result = await adapter.get("test-key")
00021|
00022| expect(result).toEqual(testData)
00023| })
00024|
00025| it("should return null for non-existent keys", async () => {
00026| const result = await adapter.get("non-existent-key")
00027| expect(result).toBeNull()
00028| })
00029|
00030| it("should delete values", async () => {
00031| const testData = { id: "test", secret: "secret123" }
00032|
00033| await adapter.put("test-key", testData)
00034| await adapter.delete("test-key")
00035| const result = await adapter.get("test-key")
00036|
00037| expect(result).toBeNull()
00038| })
00039|
00040| it("should check if values exist", async () => {
00041| const testData = { id: "test" }
00042|
00043| expect(await adapter.exists("test-key")).toBeFalse()
00044|
00045| await adapter.put("test-key", testData)
00046| expect(await adapter.exists("test-key")).toBeTrue()
00047| })
00048|
00049| it("should list values with prefix", async () => {
00050| const data1 = { id: "test1" }
00051| const data2 = { id: "test2" }
00052| const data3 = { id: "other" }
00053|
00054| await adapter.put("credentials/test1", data1)
00055| await adapter.put("credentials/test2", data2)
00056| await adapter.put("sessions/other", data3)
00057|
00058| ...<file>
00002| * Auto-generated type validation utilities for sessions API tests
00003| * Generated from SDK types - DO NOT EDIT MANUALLY
00004| * Keep this file in sync when SDK types change
00005| */
00006|
00007| import type { Session, Message, Part, FileDiff, Model } from "../src/types"
00008| import { expect } from "bun:test"
00009|
00010| /**
00011| * Type validation utility that automatically updates with SDK changes
00012| */
00013| export function validateTypeStructure(obj: any, typeName: string, expectedProperties: string[]) {
00014| expectedProperties.forEach((prop) => {
00015| expect(obj).toHaveProperty(prop)
00016| })
00017| return true
00018| }
00019|
00020| /**
00021| * Automatically generated property lists from SDK types
00022| * These will update when SDK types change
00023| */
00024|
00025| export const SESSION_PROPERTIES = ["id", "projectID", "directory", "title", "version", "time"]
00026| export const MESSAGE_PROPERTIES = ["id", "sessionID", "role", "time"]
00027| export const PART_PROPERTIES = ["id", "sessionID", "messageID", "type", "text"]
00028| export const MODEL_PROPERTIES = ["id", "providerID", "api", "name", "capabilities", "cost", "limit", "status"]
00029| export const FILEDIFF_PROPERTIES = ["file", "before", "after", "additions", "deletions"]
00030|
00031| /**
00032| * Validate that an object matches the structure of a specific SDK type
00033| */
00034|
00035| export function validateSessionStructure(obj: any) {
00036| return validateTypeStructure(obj, "Session", SESSION_PROPERTIES)
00037| }
00038| export function validateMessageStructure(obj: any) {
00039| return validateTypeStructure(obj, "Message", MESSAGE_PROPERTIES)
00040| }
00041| export function validatePartStructure(obj: any) {
00042| return validateTypeStructure(obj, "Part", PART_PROPERTIES)
00043| }
00044| export function validateModelStructure(obj: any) {
00045| return validateTypeStructure(obj, "Model", MODEL_PROPERTIES)
00046| }
00047| exp...<file>
00002| * SDK Types for OpenCode Agent Sessions
00003| * Import core types from @opencode-ai/sdk/v2
00004| */
00005| import type { Session, Message, Part, FileDiff, Model } from "@opencode-ai/sdk/v2"
00006|
00007| // Re-export SDK types for convenience
00008| export type { Session, Message, Part, FileDiff, Model }
00009|
00010| /**
00011| * Sync data discriminated union type
00012| * Represents the different types of data that can be synchronized
00013| */
00014| export type SyncData =
00015| | { type: "session"; data: Session }
00016| | { type: "message"; data: Message }
00017| | { type: "part"; data: Part }
00018| | { type: "session_diff"; data: FileDiff[] }
00019| | { type: "model"; data: Model[] }
00020|
00021| /**
00022| * Complete agent session structure
00023| * This represents a fully reconstructed session from sync data
00024| */
00025| export type SessionMetadata = {
00026| createdAt: number
00027| lastUpdated: number
00028| syncCount: number
00029| secret: string
00030| sessionID: string
00031| }
00032|
00033| export type AgentSession = {
00034| session: Session
00035| messages: Message[]
00036| parts: Part[]
00037| diffs: FileDiff[]
00038| models: Model[]
00039| metadata: SessionMetadata
00040| }
00041|
00042| /**
00043| * Share credentials
00044| */
00045| export type SyncInfo = {
00046| id: string
00047| url: string
00048| secret: string
00049| }
00050|
00051| export type ShareCredentials = SyncInfo & {
00052| sessionID: string
00053| createdAt: number
00054| }
00055|
00056| export type SecretNS = string
00057|
(End of file - total 57 lines)
</file><file>
00002| import { cors } from "hono/cors"
00003| import { zValidator } from "@hono/zod-validator"
00004| import { z } from "zod"
00005| import { v5 as uuidv5 } from "uuid"
00006| import type { SyncInfo, ShareCredentials, SyncData, AgentSession, Session } from "./types"
00007| import { createStorageAdapter, type StorageAdapter } from "./storage"
00008|
00009| /**
00010| * Environment bindings for Cloudflare Worker
00011| */
00012| type Env = {
00013| SESSIONS_STORE: R2Bucket
00014| SESSIONS_SHARED_SECRET: string
00015| API_DOMAIN: string
00016| }
00017|
00018| /**
00019| * Main Hono application
00020| */
00021| const app = new Hono<{ Bindings: Env }>()
00022|
00023| // Enable CORS for all routes
00024| app.use("/*", cors())
00025|
00026| /**
00027| * Create a storage adapter from the R2 bucket binding
00028| */
00029| function getStorageAdapter(c: any): {
00030| sessions: StorageAdapter<AgentSession>
00031| } {
00032| const bucket = c.env.SESSIONS_STORE
00033| return {
00034| // credentials: createStorageAdapter<ShareCredentials>(bucket),
00035| sessions: createStorageAdapter<AgentSession>(bucket),
00036| }
00037| }
00038|
00039| /**
00040| * Health check endpoint
00041| */
00042| app.get("/", (c) => {
00043| return c.json({
00044| service: "opencode-sessions-api",
00045| version: "1.0.0",
00046| status: "healthy",
00047| })
00048| })
00049|
00050| /**
00051| * Create a new share
00052| * POST /api/share
00053| */
00054| app.post(
00055| "/api/share",
00056| zValidator(
00057| "json",
00058| z.object({
00059| sessionID: z.string(),
00060| }),
00061| ),
00062| async (c) => {
00063| const { sessionID } = c.req.valid("json")
00064| const { sessions } = getStorageAdapter(c)
00065|
00066| const shareID = sessionID.slice(-8)
00067|
00068| const shareSecret = uuidv5(sessionID, c.env.SESSIONS_SHARED_SECRET)
00069| const now = Date.now()
00070|
00071| const info: S...<file>
00121| {
00122| method: "DELETE",
00123| headers: { "Content-Type": "application/json" },
00124| body: JSON.stringify({ secret }),
00125| },
00126| env,
00127| )
00128| return { response, data: await parseJson<{ success?: boolean; error?: string }>(response) }
00129| }
00130|
00131| async function getShare(shareID: string, env: TestEnv) {
00132| const response = await request(`http://localhost/api/share/${shareID}`, { method: "GET" }, env)
00133| if (!response.ok) {
00134| return { response, data: null }
00135| }
00136| return { response, data: await parseJson<AgentSession>(response) }
00137| }
00138|
00139| async function getMetadata(shareID: string, env: TestEnv) {
00140| const response = await request(`http://localhost/api/share/${shareID}/metadata`, { method: "GET" }, env)
00141| if (!response.ok) {
00142| return { response, data: null }
00143| }
00144| return { response, data: await parseJson<Record<string, unknown>>(response) }
00145| }
00146|
00147| async function listSessions(env: TestEnv) {
00148| const response = await request("http://localhost/api/sessions", { method: "GET" }, env)
00149| return {
00150| response,
00151| data: await parseJson<{ sessions: Array<{ id: string; sessionID: string; createdAt: number }>; count: number }>(
00152| response,
00153| ),
00154| }
00155| }
00156|
00157| describe("POST /api/share", () => {
00158| let env: TestEnv
00159|
00160| beforeEach(() => {
00161| env = createEnv()
00162| })
00163|
00164| test("creates share with valid sessionID", async () => {
00165| const { response, data } = await createShare("session-abc123", env)
00166| expect(response.status).toBe(200)
00167| expect(data.id).toBe("n-abc123")
00168| expect(data.secret).toBeDefined()
00169| expect(data.url).toBe(`https://opencode.j9xym.com/share/n-abc123`)
00170| })
00171|
00172| test("returns {id, url, secret} with correct types",...<file>
00002| * Storage Adapter for R2 Bucket
00003| * Typeclass-based abstraction for R2 storage operations
00004| */
00005|
00006| // R2 Types are available globally from worker-configuration.d.ts (generated by Wrangler)
00007| /**
00008| * Storage Adapter Interface - Typeclass for storage operations
00009| * This defines the contract that any storage implementation must satisfy
00010| */
00011| export interface StorageAdapter<T> {
00012| /**
00013| * Put an object into storage
00014| * @param key - The key/path where to store the object
00015| * @param value - The value to store (will be serialized to JSON)
00016| * @param options - Optional storage options
00017| */
00018| put(key: string, value: T, options?: R2PutOptions): Promise<void>
00019|
00020| /**
00021| * Get an object from storage
00022| * @param key - The key/path of the object to retrieve
00023| * @returns The retrieved object or null if not found
00024| */
00025| get(key: string): Promise<T | null>
00026|
00027| /**
00028| * Delete an object from storage
00029| * @param key - The key/path of the object to delete
00030| */
00031| delete(key: string): Promise<void>
00032|
00033| /**
00034| * List objects in storage with optional prefix
00035| * @param options - List options including prefix
00036| * @returns List of objects with their keys
00037| */
00038| list(options?: R2ListOptions): Promise<R2Object[]>
00039|
00040| /**
00041| * Check if an object exists in storage
00042| * @param key - The key/path to check
00043| * @returns True if the object exists, false otherwise
00044| */
00045| exists(key: string): Promise<boolean>
00046| }
00047|
00048| /**
00049| * R2 Storage Adapter Implementation
00050| * Concrete implementation of StorageAdapter for Cloudflare R2
00051| */
00052| export class R2StorageAdapter<T> implements StorageAdapter<T> {
00053| constructor(private readonly bucket: R2Bucket) {}
00054|
...Found 3 matches
/home/jm/data/code/opencode/packages/sdk/js/src/v2/gen/types.gen.ts:
Line 226: export type Message = UserMessage | AssistantMessage
Line 243: export type TextPart = {
Line 1993: export type TextPartInput = {<file>
00211| summary?: boolean
00212| cost: number
00213| tokens: {
00214| input: number
00215| output: number
00216| reasoning: number
00217| cache: {
00218| read: number
00219| write: number
00220| }
00221| }
00222| variant?: string
00223| finish?: string
00224| }
00225|
00226| export type Message = UserMessage | AssistantMessage
00227|
00228| export type EventMessageUpdated = {
00229| type: "message.updated"
00230| properties: {
00231| info: Message
00232| }
00233| }
00234|
00235| export type EventMessageRemoved = {
00236| type: "message.removed"
00237| properties: {
00238| sessionID: string
00239| messageID: string
00240| }
00241| }
00242|
00243| export type TextPart = {
00244| id: string
00245| sessionID: string
00246| messageID: string
00247| type: "text"
00248| text: string
00249| synthetic?: boolean
00250| ignored?: boolean
00251| time?: {
00252| start: number
00253| end?: number
00254| }
00255| metadata?: {
00256| [key: string]: unknown
00257| }
00258| }
00259|
00260| export type SubtaskPart = {
00261| id: string
00262| sessionID: string
00263| messageID: string
00264| type: "subtask"
00265| prompt: string
00266| description: string
00267| agent: string
00268| model?: {
00269| providerID: string
00270| modelID: string
00271| }
00272| command?: string
00273| }
00274|
00275| export type ReasoningPart = {
00276| id: string
00277| sessionID: string
00278| messageID: string
00279| type: "reasoning"
00280| text: string
00281| metadata?: {
00282| [key: string]: unknown
00283| }
00284| time: {
00285| start: number
00286| end?: number
00287| }
00288| }
00289|
00290| export type FilePartSourceText = {
00291| value: string
00292| start: number
00293| end: number
00294| }
00295|
00296| export type FileSource = {
00297| text: FilePartSourceText
00298| type: "file"
00299| path: string
00300| }...<file>
01861| export type NotFoundError = {
01862| name: "NotFoundError"
01863| data: {
01864| message: string
01865| }
01866| }
01867|
01868| export type Model = {
01869| id: string
01870| providerID: string
01871| api: {
01872| id: string
01873| url: string
01874| npm: string
01875| }
01876| name: string
01877| family?: string
01878| capabilities: {
01879| temperature: boolean
01880| reasoning: boolean
01881| attachment: boolean
01882| toolcall: boolean
01883| input: {
01884| text: boolean
01885| audio: boolean
01886| image: boolean
01887| video: boolean
01888| pdf: boolean
01889| }
01890| output: {
01891| text: boolean
01892| audio: boolean
01893| image: boolean
01894| video: boolean
01895| pdf: boolean
01896| }
01897| interleaved:
01898| | boolean
01899| | {
01900| field: "reasoning_content" | "reasoning_details"
01901| }
01902| }
01903| cost: {
01904| input: number
01905| output: number
01906| cache: {
01907| read: number
01908| write: number
01909| }
01910| experimentalOver200K?: {
01911| input: number
01912| output: number
01913| cache: {
01914| read: number
01915| write: number
01916| }
01917| }
01918| }
01919| limit: {
01920| context: number
01921| input?: number
01922| output: number
01923| }
01924| status: "alpha" | "beta" | "deprecated" | "active"
01925| options: {
01926| [key: string]: unknown
01927| }
01928| headers: {
01929| [key: string]: string
01930| }
01931| release_date: string
01932| variants?: {
01933| [key: string]: {
01934| [key: string]: unknown
01935| }
01936| }
01937| }
01938|
01939| export type Provider = {
01940| id: string
01941| name: string
01942| source: "env" | "config" | "custom" | "api"
01943| env: Array<string>
01944| key?: s...<file>
00001| import { describe, expect, test, beforeEach } from "bun:test"
00002| import app from "./index"
00003| import type { AgentSession, SyncInfo } from "./types"
00004| import { createTestFileDiff, createTestMessage, createTestModel, createTestPart, createTestSession } from "./test-utils"
00005|
00006| const SHARED_SECRET = "6ba7b810-9dad-11d1-80b4-00c04fd430c8"
00007|
00008| type TestEnv = {
00009| SESSIONS_STORE: R2Bucket
00010| SESSIONS_SHARED_SECRET: string
00011| API_DOMAIN: string
00012| }
00013|
00014| function createMockR2Bucket() {
00015| const storage = new Map<string, string>()
00016|
00017| return {
00018| put: async (key: string, value: string | ArrayBuffer | ReadableStream) => {
00019| let content = ""
00020| if (typeof value === "string") {
(File has more lines. Use 'offset' parameter to read beyond line 20)
</file><file>
00001| import { describe, expect, test } from "bun:test"
00002| import app from "./index"
00003| import type { AgentSession, SyncInfo } from "./types"
00004| import {
00005| createTestFileDiff,
00006| createTestMessage,
00007| createTestModel,
00008| createTestPart,
00009| createTestSession,
00010| SESSION_PROPERTIES,
00011| validateTypeStructure,
00012| } from "./test-utils"
00013|
00014| const sharedSecret = "6ba7b810-9dad-11d1-80b4-00c04fd430c8"
00015|
00016| const createMockR2Bucket = () => {
00017| const storage = new Map<string, string>()
00018|
00019| const mockBucket: any = {
00020| put: async (key: string, value: string | ArrayBuffer) => {
(File has more lines. Use 'offset' parameter to read beyond line 20)
</file><file>
00001| /**
00002| * Auto-generated type validation utilities for sessions API tests
00003| * Generated from SDK types - DO NOT EDIT MANUALLY
00004| * Keep this file in sync when SDK types change
00005| */
00006|
00007| import type { Session, Message, Part, FileDiff, Model } from "../src/types"
00008| import { expect } from "bun:test"
00009|
00010| /**
00011| * Type validation utility that automatically updates with SDK changes
00012| */
00013| export function validateTypeStructure(obj: any, typeName: string, expectedProperties: string[]) {
00014| expectedProperties.forEach((prop) => {
00015| expect(obj).toHaveProperty(prop)
00016| })
00017| return true
00018| }
00019|
00020| /**
(File has more lines. Use 'offset' parameter to read beyond line 20)
</file>Success. Updated the following files:
M packages/sessions/tsconfig.json<file>
00151| providerID: string
00152| message: string
00153| }
00154| }
00155|
00156| export type UnknownError = {
00157| name: "UnknownError"
00158| data: {
00159| message: string
00160| }
00161| }
00162|
00163| export type MessageOutputLengthError = {
00164| name: "MessageOutputLengthError"
00165| data: {
00166| [key: string]: unknown
00167| }
00168| }
00169|
00170| export type MessageAbortedError = {
00171| name: "MessageAbortedError"
00172| data: {
00173| message: string
00174| }
00175| }
00176|
00177| export type ApiError = {
00178| name: "APIError"
00179| data: {
00180| message: string
00181| statusCode?: number
00182| isRetryable: boolean
00183| responseHeaders?: {
00184| [key: string]: string
00185| }
00186| responseBody?: string
00187| metadata?: {
00188| [key: string]: string
00189| }
00190| }
00191| }
00192|
00193| export type AssistantMessage = {
00194| id: string
00195| sessionID: string
00196| role: "assistant"
00197| time: {
00198| created: number
00199| completed?: number
00200| }
00201| error?: ProviderAuthError | UnknownError | MessageOutputLengthError | MessageAbortedError | ApiError
00202| parentID: string
00203| modelID: string
00204| providerID: string
00205| mode: string
00206| agent: string
00207| path: {
00208| cwd: string
00209| root: string
00210| }
00211| summary?: boolean
00212| cost: number
00213| tokens: {
00214| input: number
00215| output: number
00216| reasoning: number
00217| cache: {
00218| read: number
00219| write: number
00220| }
00221| }
00222| variant?: string
00223| finish?: string
00224| }
00225|
00226| export type Message = UserMessage | AssistantMessage
00227|
00228| export type EventMessageUpdated = {
00229| type: "message.updated"
00230| properties: {
(File has more lines. Use 'offset' parameter to read beyond line 230)
</file><file>
00121| [key: string]: boolean
00122| }
00123| variant?: string
00124| }
00125|
00126| export type ProviderAuthError = {
00127| name: "ProviderAuthError"
00128| data: {
00129| providerID: string
00130| message: string
00131| }
00132| }
00133|
00134| export type UnknownError = {
00135| name: "UnknownError"
00136| data: {
00137| message: string
00138| }
00139| }
00140|
00141| export type MessageOutputLengthError = {
00142| name: "MessageOutputLengthError"
00143| data: {
00144| [key: string]: unknown
00145| }
00146| }
00147|
00148| export type MessageAbortedError = {
00149| name: "MessageAbortedError"
00150| data: {
00151| message: string
00152| }
00153| }
00154|
00155| export type ApiError = {
00156| name: "APIError"
00157| data: {
00158| message: string
00159| statusCode?: number
00160| isRetryable: boolean
(File has more lines. Use 'offset' parameter to read beyond line 160)
</file>Found 24 matches
/home/jm/data/code/opencode/packages/sessions/src/api.test.ts:
Line 253: expect(session?.messages).toHaveLength(2)
Line 254: expect(session?.messages[0].id).toBe("msg-1")
Line 255: expect(session?.messages[1].id).toBe("msg-2")
Line 274: expect(session?.messages).toHaveLength(1)
Line 275: expect(session?.messages[0].role).toBe("assistant")
Line 294: expect(session?.parts).toHaveLength(2)
Line 313: expect(session?.parts).toHaveLength(1)
Line 314: expect(session?.parts[0].text).toBe("updated")
Line 334: expect(session?.diffs).toHaveLength(3)
Line 348: expect(session?.models).toHaveLength(2)
Line 367: expect(session?.models).toHaveLength(1)
Line 368: expect(session?.models[0].name).toBe("GPT-4 Turbo")
Line 398: const ts1 = session1?.metadata.lastUpdated
Line 404: const ts2 = session2?.metadata.lastUpdated
Line 489: expect(data?.session.id).toBe("get-test")
Line 490: expect(data?.session.title).toBe("Test Session")
Line 491: expect(data?.messages).toHaveLength(1)
Line 492: expect(data?.parts).toHaveLength(1)
Line 493: expect(data?.diffs).toHaveLength(1)
Line 494: expect(data?.models).toHaveLength(1)
Line 495: expect(data?.metadata.syncCount).toBe(1)
Line 547: expect(data.sessions[0].sessionID).toBe("session-fields")
Line 548: expect(typeof data.sessions[0].createdAt).toBe("number")
Line 581: expect(data?.sessionID).toBe("meta-test")Found 1 matches
/home/jm/data/code/opencode/packages/sessions/src/types.ts:
Line 56: export type SecretNS = stringSuccess. Updated the following files:
M packages/sessions/src/test-utils.ts
LSP errors detected in packages/sessions/src/test-utils.ts, please fix:
<diagnostics file="/home/jm/data/code/opencode/packages/sessions/src/test-utils.ts">
ERROR [82:3] Type '{ id: string; sessionID: string; role: "user"; time: { created: number; }; summary?: { title?: string | undefined; body?: string | undefined; diffs: FileDiff[]; } | undefined; agent: string; model: { ...; }; system?: string | undefined; tools?: { ...; } | undefined; variant?: string | undefined; } | { ...; }' is not assignable to type 'Message'.
Type '{ id: string; sessionID: string; role: "user" | "assistant"; time: { created: number; }; error?: (ProviderAuthError | UnknownError | MessageOutputLengthError | MessageAbortedError | ApiError) | undefined; ... 13 more ...; tools?: { [key: string]: boolean; }; }' is not assignable to type 'Message'.
Type '{ id: string; sessionID: string; role: "user" | "assistant"; time: { created: number; }; error?: (ProviderAuthError | UnknownError | MessageOutputLengthError | MessageAbortedError | ApiError) | undefined; ... 13 more ...; tools?: { [key: string]: boolean; }; }' is not assignable to type 'AssistantMessage'.
Types of property 'role' are incompatible.
Type '"user" | "assistant"' is not assignable to type '"assistant"'.
Type '"user"' is not assignable to type '"assistant"'.
ERROR [93:3] Type '{ id: string; sessionID: string; messageID: string; type: "text"; text: string; synthetic?: boolean | undefined; ignored?: boolean | undefined; time?: { start: number; end?: number | undefined; } | undefined; metadata?: { ...; } | undefined; } | ... 10 more ... | { ...; }' is not assignable to type 'Part'.
Type '{ id: string; sessionID: string; messageID: string; type: "text" | "subtask"; prompt?: string | undefined; description?: string | undefined; agent?: string | undefined; model?: { providerID: string; modelID: string; } | undefined; ... 5 more ...; m...<file>
00241| const { data: share } = await createShare("msg-test", env)
00242| const msg1 = createTestMessage({ id: "msg-1", sessionID: "msg-test" })
00243| const msg2 = createTestMessage({ id: "msg-2", sessionID: "msg-test" })
00244|
00245| await syncShare(share.id, env, {
00246| secret: share.secret,
00247| data: [{ type: "message", data: msg1 }],
00248| })
00249|
00250| await syncShare(share.id, env, {
00251| secret: share.secret,
00252| data: [{ type: "message", data: msg2 }],
00253| })
00254|
00255| const { data: session } = await getShare(share.id, env)
00256| if (!session) throw new Error("Expected session")
00257| expect(session.messages).toHaveLength(2)
00258| expect(session.messages[0].id).toBe("msg-1")
00259| expect(session.messages[1].id).toBe("msg-2")
00260| })
00261|
00262| test("updates existing message with same ID", async () => {
00263| const { data: share } = await createShare("msg-update", env)
00264| const msg = createTestMessage({ id: "msg-1", sessionID: "msg-update", role: "user" })
00265|
00266| await syncShare(share.id, env, {
00267| secret: share.secret,
00268| data: [{ type: "message", data: msg }],
00269| })
00270|
00271| const updatedMsg = { ...msg, role: "assistant" }
00272| await syncShare(share.id, env, {
00273| secret: share.secret,
00274| data: [{ type: "message", data: updatedMsg }],
00275| })
00276|
00277| const { data: session } = await getShare(share.id, env)
00278| if (!session) throw new Error("Expected session")
00279| expect(session.messages).toHaveLength(1)
00280| expect(session.messages[0].role).toBe("assistant")
00281| })
00282|
00283| test("appends new parts", async () => {
00284| const { data: share } = await createShare("part-test", env)
00285| const part1 = createTestPart({ id: "part-1", sessionID: "part-test", messageID: "msg-1" })
00286| const part2 =...<file>
00361| })
00362|
00363| test("updates existing model with same ID", async () => {
00364| const { data: share } = await createShare("model-update", env)
00365| const model = createTestModel({ id: "gpt-4", providerID: "openai", name: "GPT-4" })
00366|
00367| await syncShare(share.id, env, {
00368| secret: share.secret,
00369| data: [{ type: "model", data: [model] }],
00370| })
00371|
00372| const updatedModel = { ...model, name: "GPT-4 Turbo" }
00373| await syncShare(share.id, env, {
00374| secret: share.secret,
00375| data: [{ type: "model", data: [updatedModel] }],
00376| })
00377|
00378| const { data: session } = await getShare(share.id, env)
00379| if (!session) throw new Error("Expected session")
00380| expect(session.models).toHaveLength(1)
00381| expect(session.models[0].name).toBe("GPT-4 Turbo")
00382| })
00383|
00384| test("increments syncCount on each sync", async () => {
00385| const { data: share } = await createShare("count-test", env)
00386|
00387| const r1 = await syncShare(share.id, env, {
00388| secret: share.secret,
00389| data: [{ type: "session", data: createTestSession() }],
00390| })
00391| expect(r1.data.syncCount).toBe(1)
00392|
00393| const r2 = await syncShare(share.id, env, {
00394| secret: share.secret,
00395| data: [{ type: "session", data: createTestSession() }],
00396| })
00397| expect(r2.data.syncCount).toBe(2)
00398|
00399| const r3 = await syncShare(share.id, env, {
00400| secret: share.secret,
(File has more lines. Use 'offset' parameter to read beyond line 400)
</file><file>
00091| {
00092| method: "POST",
00093| headers: { "Content-Type": "application/json" },
00094| body: JSON.stringify({ sessionID }),
00095| },
00096| env,
00097| )
00098| return { response, data: await parseJson<SyncInfo>(response) }
00099| }
00100|
00101| async function syncShare(
00102| shareID: string,
00103| env: TestEnv,
00104| payload: {
00105| secret: string
00106| data: AgentSession["messages"] extends never ? never : Array<{ type: string; data: unknown }>
00107| },
00108| ) {
00109| const response = await request(
00110| `http://localhost/api/share/${shareID}/sync`,
00111| {
00112| method: "POST",
00113| headers: { "Content-Type": "application/json" },
00114| body: JSON.stringify(payload),
00115| },
00116| env,
00117| )
00118| return { response, data: await parseJson<{ success: boolean; syncCount: number; error?: string }>(response) }
00119| }
00120|
(File has more lines. Use 'offset' parameter to read beyond line 120)
</file><file>
00247| await syncShare(share.id, env, {
00248| secret: share.secret,
00249| data: [{ type: "message", data: msg2 }],
00250| })
00251|
00252| const { data: session } = await getShare(share.id, env)
00253| if (!session) throw new Error("Expected session")
00254| expect(session.messages).toHaveLength(2)
00255| expect(session.messages[0].id).toBe("msg-1")
00256| expect(session.messages[1].id).toBe("msg-2")
00257| })
00258|
00259| test("updates existing message with same ID", async () => {
00260| const { data: share } = await createShare("msg-update", env)
00261| const msg = createTestMessage({ id: "msg-1", sessionID: "msg-update", role: "user" })
00262|
00263| await syncShare(share.id, env, {
00264| secret: share.secret,
00265| data: [{ type: "message", data: msg }],
00266| })
00267|
00268| const updatedMsg = { ...msg, role: "assistant" }
00269| await syncShare(share.id, env, {
00270| secret: share.secret,
00271| data: [{ type: "message", data: updatedMsg }],
00272| })
00273|
00274| const { data: session } = await getShare(share.id, env)
00275| if (!session) throw new Error("Expected session")
00276| expect(session.messages).toHaveLength(1)
00277| expect(session.messages[0].role).toBe("assistant")
00278| })
00279|
00280| test("appends new parts", async () => {
00281| const { data: share } = await createShare("part-test", env)
00282| const part1 = createTestPart({ id: "part-1", sessionID: "part-test", messageID: "msg-1" })
00283| const part2 = createTestPart({ id: "part-2", sessionID: "part-test", messageID: "msg-1" })
00284|
00285| await syncShare(share.id, env, {
00286| secret: share.secret,
(File has more lines. Use 'offset' parameter to read beyond line 286)
</file><file>
00293| })
00294|
00295| const { data: session } = await getShare(share.id, env)
00296| if (!session) throw new Error("Expected session")
00297| expect(session.parts).toHaveLength(2)
00298| })
00299|
00300| test("updates existing part with same ID", async () => {
00301| const { data: share } = await createShare("part-update", env)
00302| const part = createTestPart({ id: "part-1", sessionID: "part-update", messageID: "msg-1", text: "original" })
00303|
00304| await syncShare(share.id, env, {
00305| secret: share.secret,
00306| data: [{ type: "part", data: part }],
00307| })
00308|
00309| const updatedPart = { ...part, text: "updated" }
00310| await syncShare(share.id, env, {
00311| secret: share.secret,
00312| data: [{ type: "part", data: updatedPart }],
00313| })
00314|
00315| const { data: session } = await getShare(share.id, env)
00316| if (!session) throw new Error("Expected session")
00317| expect(session.parts).toHaveLength(1)
00318| const part = session.parts[0]
00319| if (!part) throw new Error("Expected part")
00320| if (part.type !== "text") throw new Error("Expected text part")
00321| expect(part.text).toBe("updated")
00322| })
00323|
00324| test("accumulates diffs (appends, never dedupes)", async () => {
00325| const { data: share } = await createShare("diff-test", env)
00326| const diff1 = createTestFileDiff({ file: "file1.ts" })
00327| const diff2 = createTestFileDiff({ file: "file2.ts" })
00328| const diff3 = createTestFileDiff({ file: "file1.ts" })
00329|
00330| await syncShare(share.id, env, {
00331| secret: share.secret,
00332| data: [{ type: "session_diff", data: [diff1, diff2] }],
(File has more lines. Use 'offset' parameter to read beyond line 332)
</file><file>
00371| secret: share.secret,
00372| data: [{ type: "model", data: [updatedModel] }],
00373| })
00374|
00375| const { data: session } = await getShare(share.id, env)
00376| if (!session) throw new Error("Expected session")
00377| expect(session.models).toHaveLength(1)
00378| expect(session.models[0].name).toBe("GPT-4 Turbo")
00379| })
00380|
00381| test("increments syncCount on each sync", async () => {
00382| const { data: share } = await createShare("count-test", env)
00383|
00384| const r1 = await syncShare(share.id, env, {
00385| secret: share.secret,
00386| data: [{ type: "session", data: createTestSession() }],
00387| })
00388| expect(r1.data.syncCount).toBe(1)
00389|
00390| const r2 = await syncShare(share.id, env, {
(File has more lines. Use 'offset' parameter to read beyond line 390)
</file>Success. Updated the following files:
M packages/sessions/src/index.ts$ wrangler types && tsc --noEmit
⛅️ wrangler 4.60.0 (update available 4.63.0)
─────────────────────────────────────────────
Generating project types...
declare namespace Cloudflare {
interface GlobalProps {
mainModule: typeof import("./src/index");
}
interface Env {
SESSIONS_STORE: R2Bucket;
SESSIONS_SHARED_SECRET: string;
API_DOMAIN: string;
}
}
interface Env extends Cloudflare.Env {}
type StringifyValues<EnvType extends Record<string, unknown>> = {
[Binding in keyof EnvType]: EnvType[Binding] extends string ? EnvType[Binding] : string;
};
declare namespace NodeJS {
interface ProcessEnv extends StringifyValues<Pick<Cloudflare.Env, "SESSIONS_SHARED_SECRET" | "API_DOMAIN">> {}
}
Generating runtime types...
Runtime types generated.
✨ Types written to worker-configuration.d.ts
📖 Read about runtime types
https://developers.cloudflare.com/workers/languages/typescript/#generate-types
📣 Remember to rerun 'wrangler types' after you change your wrangler.jsonc file.
src/storage.test.ts(60,12): error TS2532: Object is possibly 'undefined'.
src/storage.test.ts(61,12): error TS2532: Object is possibly 'undefined'.
src/test-utils.ts(55,9): error TS2741: Property 'slug' is missing in type '{ id: string; projectID: string; directory: string; title: string; version: string; time: { created: number; updated: number; }; }' but required in type 'Session'.
<file>
00049| }
00050|
00051| /**
00052| * Create properly typed test data that matches SDK types
00053| */
00054| export function createTestSession(overrides: Partial<Session> = {}): Session {
00055| const baseSession: Session = {
00056| id: "test-" + Date.now(),
00057| projectID: "test-project",
00058| directory: "/test",
00059| title: "Test Session",
00060| version: "1.0",
00061| time: {
00062| created: Date.now(),
00063| updated: Date.now()
00064| }
00065| }
00066| return { ...baseSession, ...overrides }
00067| }
00068|
00069| export function createTestMessage(overrides: Partial<Message> = {}): Message {
00070| const baseMessage: any = {
00071| id: "test-" + Date.now(),
00072| sessionID: "test-session",
00073| role: "user",
00074| time: { created: Date.now() }
00075| }
00076| return { ...baseMessage, ...overrides }
00077| }
00078|
(File has more lines. Use 'offset' parameter to read beyond line 78)
</file><file>
00053|
00054| await adapter.put("credentials/test1", data1)
00055| await adapter.put("credentials/test2", data2)
00056| await adapter.put("sessions/other", data3)
00057|
00058| const result = await adapter.list({ prefix: "credentials/" })
00059| expect(result.length).toBe(2)
00060| expect(result[0].key).toContain("credentials/")
00061| expect(result[1].key).toContain("credentials/")
00062| })
00063|
00064| it("should clear all data", async () => {
00065| const testData = { id: "test" }
00066|
00067| await adapter.put("test-key", testData)
00068| expect(await adapter.get("test-key")).not.toBeNull()
00069|
00070| adapter.clear()
00071| expect(await adapter.get("test-key")).toBeNull()
00072| })
(File has more lines. Use 'offset' parameter to read beyond line 72)
</file><file>
00001| /**
00002| * Auto-generated type validation utilities for sessions API tests
00003| * Generated from SDK types - DO NOT EDIT MANUALLY
00004| * Run 'bun generate-test-types' to update when SDK types change
00005| */
00006|
00007| import type { Session, Message, Part, FileDiff, Model } from "../src/types"
00008| import { expect } from "bun:test"
00009|
00010| /**
00011| * Type validation utility that automatically updates with SDK changes
00012| */
00013| export function validateTypeStructure(obj: any, typeName: string, expectedProperties: string[]) {
00014| expectedProperties.forEach(prop => {
00015| expect(obj).toHaveProperty(prop)
00016| })
00017| return true
00018| }
00019|
00020| /**
00021| * Automatically generated property lists from SDK types
00022| * These will update when SDK types change
00023| */
00024|
00025| export const SESSION_PROPERTIES = ["id","projectID","directory","title","version","time"]
00026| export const MESSAGE_PROPERTIES = ["id","sessionID","role","time"]
00027| export const PART_PROPERTIES = ["id","sessionID","messageID","type","text"]
00028| export const MODEL_PROPERTIES = ["id","providerID","api","name","capabilities","cost","limit","status"]
00029| export const FILEDIFF_PROPERTIES = ["file","before","after","additions","deletions"]
00030|
00031| /**
00032| * Validate that an object matches the structure of a specific SDK type
00033| */
00034|
00035| export function validateSessionStructure(obj: any) {
00036| return validateTypeStructure(obj, "Session", SESSION_PROPERTIES)
00037| }
00038| export function validateMessageStructure(obj: any) {
00039| return validateTypeStructure(obj, "Message", MESSAGE_PROPERTIES)
00040| }
00041| export function validatePartStructure(obj: any) {
00042| return validateTypeStructure(obj, "Part", PART_PROPERTIES)
00043| }
00044| export function validateModelStructure(obj: any) {
00045| return validateTypeStructure(obj, "Model", MODEL_PROPERTIES)
00046| }
00047| ...Success. Updated the following files:
M packages/sessions/src/test-utils.tsSuccess. Updated the following files:
M packages/sessions/src/storage.test.ts$ wrangler types && tsc --noEmit
⛅️ wrangler 4.60.0 (update available 4.63.0)
─────────────────────────────────────────────
Generating project types...
declare namespace Cloudflare {
interface GlobalProps {
mainModule: typeof import("./src/index");
}
interface Env {
SESSIONS_STORE: R2Bucket;
SESSIONS_SHARED_SECRET: string;
API_DOMAIN: string;
}
}
interface Env extends Cloudflare.Env {}
type StringifyValues<EnvType extends Record<string, unknown>> = {
[Binding in keyof EnvType]: EnvType[Binding] extends string ? EnvType[Binding] : string;
};
declare namespace NodeJS {
interface ProcessEnv extends StringifyValues<Pick<Cloudflare.Env, "SESSIONS_SHARED_SECRET" | "API_DOMAIN">> {}
}
Generating runtime types...
Runtime types generated.
✨ Types written to worker-configuration.d.ts
📖 Read about runtime types
https://developers.cloudflare.com/workers/languages/typescript/#generate-types
📣 Remember to rerun 'wrangler types' after you change your wrangler.jsonc file.
M bun.lock
M package.json
M packages/console/core/drizzle.config.ts
M packages/opencode/src/share/share-next.ts
D packages/sdk/gen-capnp-example.ts
M packages/sessions/README.md
M packages/sessions/examples/storage-usage.ts
D packages/sessions/linter-output
M packages/sessions/package.json
D packages/sessions/script/curl-tests.sh
AM packages/sessions/src/api.test.ts
M packages/sessions/src/index.test.ts
M packages/sessions/src/index.ts
D packages/sessions/src/index.ts.backup
M packages/sessions/src/storage.test.ts
M packages/sessions/src/test-utils.ts
D packages/sessions/src/test.ts
M packages/sessions/tsconfig.json
D packages/sessions/vitest.config.ts
D packages/sessions/worker-configuration.d.ts.md
M packages/sessions/wrangler.jsonc
M packages/ui/src/components/popover.tsx
M packages/util/src/secret.ts
M packages/web/astro.config.mjs
M packages/web/wrangler.jsonc
Implemented — I made the focused typing cleanup in packages/sessions and re-ran typecheck successfully.
bun:test resolves: packages/sessions/tsconfig.json:12Session.slug: packages/sessions/src/test-utils.ts:25, packages/sessions/src/test-utils.ts:56createTestMessage to include required user message fields (agent, model) and removed loose any: packages/sessions/src/test-utils.ts:69createTestPart as a concrete text part and cast at merge boundary to handle union overrides cleanly: packages/sessions/src/test-utils.ts:79{} as Session) by creating a minimal valid Session: packages/sessions/src/index.ts:85packages/sessions/src/api.test.ts:252, packages/sessions/src/api.test.ts:274, packages/sessions/src/api.test.ts:315, packages/sessions/src/api.test.ts:375, packages/sessions/src/api.test.ts:551Part before reading .text: packages/sessions/src/api.test.ts:320packages/sessions/src/storage.test.ts:58Validation:
bun run typecheck in packages/sessions — passes now.Note:
If you want, I can do a second pass to tighten types further by replacing remaining any in tests (validateTypeStructure helpers) with unknown + proper narrowing.