mirror of
https://github.com/wu736139669/hapi.git
synced 2026-10-18 20:59:49 +00:00
feat: add hapi resume command (#647)
This commit is contained in:
+115
-1
@@ -1,6 +1,18 @@
|
||||
import axios from 'axios'
|
||||
import type { AgentState, CreateMachineResponse, CreateSessionResponse, RunnerState, Machine, MachineMetadata, Metadata, Session } from '@/api/types'
|
||||
import { AgentStateSchema, CreateMachineResponseSchema, CreateSessionResponseSchema, RunnerStateSchema, MachineMetadataSchema, MetadataSchema } from '@/api/types'
|
||||
import type { LocalResumeTarget, ResumableSession } from '@hapi/protocol'
|
||||
import {
|
||||
AgentStateSchema,
|
||||
CreateMachineResponseSchema,
|
||||
CreateSessionResponseSchema,
|
||||
GetSessionResponseSchema,
|
||||
LocalHandoffResponseSchema,
|
||||
LocalResumeTargetResponseSchema,
|
||||
RunnerStateSchema,
|
||||
MachineMetadataSchema,
|
||||
MetadataSchema,
|
||||
ResumableSessionsResponseSchema
|
||||
} from '@/api/types'
|
||||
import { configuration } from '@/configuration'
|
||||
import { getAuthToken } from '@/api/auth'
|
||||
import { apiValidationError } from '@/utils/errorUtils'
|
||||
@@ -15,6 +27,13 @@ export class ApiClient {
|
||||
|
||||
private constructor(private readonly token: string) { }
|
||||
|
||||
private authHeaders(): Record<string, string> {
|
||||
return buildHubRequestHeaders({
|
||||
Authorization: `Bearer ${this.token}`,
|
||||
'Content-Type': 'application/json'
|
||||
})
|
||||
}
|
||||
|
||||
async getOrCreateSession(opts: {
|
||||
tag: string
|
||||
metadata: Metadata
|
||||
@@ -84,6 +103,55 @@ export class ApiClient {
|
||||
}
|
||||
}
|
||||
|
||||
async getSession(sessionId: string): Promise<Session> {
|
||||
const response = await axios.get(
|
||||
`${configuration.apiUrl}/cli/sessions/${encodeURIComponent(sessionId)}`,
|
||||
{
|
||||
headers: this.authHeaders(),
|
||||
timeout: 60_000
|
||||
}
|
||||
)
|
||||
|
||||
const parsed = GetSessionResponseSchema.safeParse(response.data)
|
||||
if (!parsed.success) {
|
||||
throw apiValidationError('Invalid /cli/sessions/:id response', response)
|
||||
}
|
||||
|
||||
const raw = parsed.data.session
|
||||
const metadata = (() => {
|
||||
if (raw.metadata == null) return null
|
||||
const parsedMetadata = MetadataSchema.safeParse(raw.metadata)
|
||||
return parsedMetadata.success ? parsedMetadata.data : null
|
||||
})()
|
||||
const agentState = (() => {
|
||||
if (raw.agentState == null) return null
|
||||
const parsedAgentState = AgentStateSchema.safeParse(raw.agentState)
|
||||
return parsedAgentState.success ? parsedAgentState.data : null
|
||||
})()
|
||||
|
||||
return {
|
||||
id: raw.id,
|
||||
namespace: raw.namespace,
|
||||
seq: raw.seq,
|
||||
createdAt: raw.createdAt,
|
||||
updatedAt: raw.updatedAt,
|
||||
active: raw.active,
|
||||
activeAt: raw.activeAt,
|
||||
metadata,
|
||||
metadataVersion: raw.metadataVersion,
|
||||
agentState,
|
||||
agentStateVersion: raw.agentStateVersion,
|
||||
thinking: raw.thinking,
|
||||
thinkingAt: raw.thinkingAt,
|
||||
todos: raw.todos,
|
||||
model: raw.model,
|
||||
modelReasoningEffort: raw.modelReasoningEffort,
|
||||
effort: raw.effort,
|
||||
permissionMode: raw.permissionMode,
|
||||
collaborationMode: raw.collaborationMode
|
||||
}
|
||||
}
|
||||
|
||||
async getOrCreateMachine(opts: {
|
||||
machineId: string
|
||||
metadata: MachineMetadata
|
||||
@@ -138,6 +206,52 @@ export class ApiClient {
|
||||
}
|
||||
}
|
||||
|
||||
async listResumableSessions(machineId?: string): Promise<ResumableSession[]> {
|
||||
const qs = machineId ? `?machineId=${encodeURIComponent(machineId)}` : ''
|
||||
const response = await axios.get(
|
||||
`${configuration.apiUrl}/cli/sessions/resumable${qs}`,
|
||||
{
|
||||
headers: this.authHeaders(),
|
||||
timeout: 60_000
|
||||
}
|
||||
)
|
||||
const parsed = ResumableSessionsResponseSchema.safeParse(response.data)
|
||||
if (!parsed.success) {
|
||||
throw apiValidationError('Invalid /cli/sessions/resumable response', response)
|
||||
}
|
||||
return parsed.data.sessions
|
||||
}
|
||||
|
||||
async getLocalResumeTarget(sessionId: string): Promise<LocalResumeTarget> {
|
||||
const response = await axios.get(
|
||||
`${configuration.apiUrl}/cli/sessions/${encodeURIComponent(sessionId)}/resume-target`,
|
||||
{
|
||||
headers: this.authHeaders(),
|
||||
timeout: 60_000
|
||||
}
|
||||
)
|
||||
const parsed = LocalResumeTargetResponseSchema.safeParse(response.data)
|
||||
if (!parsed.success) {
|
||||
throw apiValidationError('Invalid /cli/sessions/:id/resume-target response', response)
|
||||
}
|
||||
return parsed.data.target
|
||||
}
|
||||
|
||||
async handoffSessionToLocal(sessionId: string): Promise<void> {
|
||||
const response = await axios.post(
|
||||
`${configuration.apiUrl}/cli/sessions/${encodeURIComponent(sessionId)}/handoff-local`,
|
||||
{},
|
||||
{
|
||||
headers: this.authHeaders(),
|
||||
timeout: 60_000
|
||||
}
|
||||
)
|
||||
const parsed = LocalHandoffResponseSchema.safeParse(response.data)
|
||||
if (!parsed.success || !parsed.data.ok) {
|
||||
throw apiValidationError('Invalid /cli/sessions/:id/handoff-local response', response)
|
||||
}
|
||||
}
|
||||
|
||||
sessionSyncClient(session: Session): ApiSessionClient {
|
||||
return new ApiSessionClient(this.token, session)
|
||||
}
|
||||
|
||||
@@ -6,6 +6,11 @@ import {
|
||||
PermissionModeSchema,
|
||||
TodosSchema
|
||||
} from '@hapi/protocol/schemas'
|
||||
import {
|
||||
LocalHandoffResponseSchema,
|
||||
LocalResumeTargetResponseSchema,
|
||||
ResumableSessionsResponseSchema
|
||||
} from '@hapi/protocol'
|
||||
import type { CodexCollaborationMode, PermissionMode } from '@hapi/protocol/types'
|
||||
import { z } from 'zod'
|
||||
import { UsageSchema } from '@/claude/types'
|
||||
@@ -142,6 +147,15 @@ export const CreateMachineResponseSchema = z.object({
|
||||
|
||||
export type CreateMachineResponse = z.infer<typeof CreateMachineResponseSchema>
|
||||
|
||||
export const GetSessionResponseSchema = CreateSessionResponseSchema
|
||||
export type GetSessionResponse = z.infer<typeof GetSessionResponseSchema>
|
||||
|
||||
export {
|
||||
LocalHandoffResponseSchema,
|
||||
LocalResumeTargetResponseSchema,
|
||||
ResumableSessionsResponseSchema
|
||||
}
|
||||
|
||||
export const MessageMetaSchema = z.object({
|
||||
sentFrom: z.string().optional(),
|
||||
fallbackModel: z.string().nullable().optional(),
|
||||
|
||||
Reference in New Issue
Block a user