wip: tui permissions

This commit is contained in:
adamdotdevin
2025-07-31 09:34:43 -05:00
parent e7631763f3
commit 5500698734
26 changed files with 1448 additions and 179 deletions

View File

@@ -0,0 +1,44 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
export {
Permissions,
type Permission,
type PermissionRespondResponse,
type PermissionRespondParams,
} from './permissions';
export {
SessionResource,
type AssistantMessage,
type FilePart,
type FilePartInput,
type FilePartSource,
type FilePartSourceText,
type FileSource,
type Message,
type Part,
type Session,
type SnapshotPart,
type StepFinishPart,
type StepStartPart,
type SymbolSource,
type TextPart,
type TextPartInput,
type ToolPart,
type ToolStateCompleted,
type ToolStateError,
type ToolStatePending,
type ToolStateRunning,
type UserMessage,
type SessionListResponse,
type SessionDeleteResponse,
type SessionAbortResponse,
type SessionInitResponse,
type SessionMessageResponse,
type SessionMessagesResponse,
type SessionSummarizeResponse,
type SessionChatParams,
type SessionInitParams,
type SessionMessageParams,
type SessionRevertParams,
type SessionSummarizeParams,
} from './session';

View File

@@ -0,0 +1,64 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import { APIResource } from '../../core/resource';
import { APIPromise } from '../../core/api-promise';
import { RequestOptions } from '../../internal/request-options';
import { path } from '../../internal/utils/path';
export class Permissions extends APIResource {
/**
* Respond to a permission request
*/
respond(
permissionID: string,
params: PermissionRespondParams,
options?: RequestOptions,
): APIPromise<PermissionRespondResponse> {
const { id, ...body } = params;
return this._client.post(path`/session/${id}/permissions/${permissionID}`, { body, ...options });
}
}
export interface Permission {
id: string;
messageID: string;
metadata: { [key: string]: unknown };
sessionID: string;
time: Permission.Time;
title: string;
toolCallID?: string;
}
export namespace Permission {
export interface Time {
created: number;
}
}
export type PermissionRespondResponse = boolean;
export interface PermissionRespondParams {
/**
* Path param:
*/
id: string;
/**
* Body param:
*/
response: 'once' | 'always' | 'reject';
}
export declare namespace Permissions {
export {
type Permission as Permission,
type PermissionRespondResponse as PermissionRespondResponse,
type PermissionRespondParams as PermissionRespondParams,
};
}

View File

@@ -0,0 +1,645 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import { APIResource } from '../../core/resource';
import * as SessionAPI from './session';
import * as Shared from '../shared';
import * as PermissionsAPI from './permissions';
import { Permission, PermissionRespondParams, PermissionRespondResponse, Permissions } from './permissions';
import { APIPromise } from '../../core/api-promise';
import { RequestOptions } from '../../internal/request-options';
import { path } from '../../internal/utils/path';
export class SessionResource extends APIResource {
permissions: PermissionsAPI.Permissions = new PermissionsAPI.Permissions(this._client);
/**
* Create a new session
*/
create(options?: RequestOptions): APIPromise<Session> {
return this._client.post('/session', options);
}
/**
* List all sessions
*/
list(options?: RequestOptions): APIPromise<SessionListResponse> {
return this._client.get('/session', options);
}
/**
* Delete a session and all its data
*/
delete(id: string, options?: RequestOptions): APIPromise<SessionDeleteResponse> {
return this._client.delete(path`/session/${id}`, options);
}
/**
* Abort a session
*/
abort(id: string, options?: RequestOptions): APIPromise<SessionAbortResponse> {
return this._client.post(path`/session/${id}/abort`, options);
}
/**
* Create and send a new message to a session
*/
chat(id: string, body: SessionChatParams, options?: RequestOptions): APIPromise<AssistantMessage> {
return this._client.post(path`/session/${id}/message`, { body, ...options });
}
/**
* Analyze the app and create an AGENTS.md file
*/
init(id: string, body: SessionInitParams, options?: RequestOptions): APIPromise<SessionInitResponse> {
return this._client.post(path`/session/${id}/init`, { body, ...options });
}
/**
* Get a message from a session
*/
message(
messageID: string,
params: SessionMessageParams,
options?: RequestOptions,
): APIPromise<SessionMessageResponse> {
const { id } = params;
return this._client.get(path`/session/${id}/message/${messageID}`, options);
}
/**
* List messages for a session
*/
messages(id: string, options?: RequestOptions): APIPromise<SessionMessagesResponse> {
return this._client.get(path`/session/${id}/message`, options);
}
/**
* Revert a message
*/
revert(id: string, body: SessionRevertParams, options?: RequestOptions): APIPromise<Session> {
return this._client.post(path`/session/${id}/revert`, { body, ...options });
}
/**
* Share a session
*/
share(id: string, options?: RequestOptions): APIPromise<Session> {
return this._client.post(path`/session/${id}/share`, options);
}
/**
* Summarize the session
*/
summarize(
id: string,
body: SessionSummarizeParams,
options?: RequestOptions,
): APIPromise<SessionSummarizeResponse> {
return this._client.post(path`/session/${id}/summarize`, { body, ...options });
}
/**
* Restore all reverted messages
*/
unrevert(id: string, options?: RequestOptions): APIPromise<Session> {
return this._client.post(path`/session/${id}/unrevert`, options);
}
/**
* Unshare the session
*/
unshare(id: string, options?: RequestOptions): APIPromise<Session> {
return this._client.delete(path`/session/${id}/share`, options);
}
}
export interface AssistantMessage {
id: string;
cost: number;
mode: string;
modelID: string;
path: AssistantMessage.Path;
providerID: string;
role: 'assistant';
sessionID: string;
system: Array<string>;
time: AssistantMessage.Time;
tokens: AssistantMessage.Tokens;
error?:
| Shared.ProviderAuthError
| Shared.UnknownError
| AssistantMessage.MessageOutputLengthError
| Shared.MessageAbortedError;
summary?: boolean;
}
export namespace AssistantMessage {
export interface Path {
cwd: string;
root: string;
}
export interface Time {
created: number;
completed?: number;
}
export interface Tokens {
cache: Tokens.Cache;
input: number;
output: number;
reasoning: number;
}
export namespace Tokens {
export interface Cache {
read: number;
write: number;
}
}
export interface MessageOutputLengthError {
data: unknown;
name: 'MessageOutputLengthError';
}
}
export interface FilePart {
id: string;
messageID: string;
mime: string;
sessionID: string;
type: 'file';
url: string;
filename?: string;
source?: FilePartSource;
}
export interface FilePartInput {
mime: string;
type: 'file';
url: string;
id?: string;
filename?: string;
source?: FilePartSource;
}
export type FilePartSource = FileSource | SymbolSource;
export interface FilePartSourceText {
end: number;
start: number;
value: string;
}
export interface FileSource {
path: string;
text: FilePartSourceText;
type: 'file';
}
export type Message = UserMessage | AssistantMessage;
export type Part =
| TextPart
| FilePart
| ToolPart
| StepStartPart
| StepFinishPart
| SnapshotPart
| Part.PatchPart;
export namespace Part {
export interface PatchPart {
id: string;
files: Array<string>;
hash: string;
messageID: string;
sessionID: string;
type: 'patch';
}
}
export interface Session {
id: string;
time: Session.Time;
title: string;
version: string;
parentID?: string;
revert?: Session.Revert;
share?: Session.Share;
}
export namespace Session {
export interface Time {
created: number;
updated: number;
}
export interface Revert {
messageID: string;
diff?: string;
partID?: string;
snapshot?: string;
}
export interface Share {
url: string;
}
}
export interface SnapshotPart {
id: string;
messageID: string;
sessionID: string;
snapshot: string;
type: 'snapshot';
}
export interface StepFinishPart {
id: string;
cost: number;
messageID: string;
sessionID: string;
tokens: StepFinishPart.Tokens;
type: 'step-finish';
}
export namespace StepFinishPart {
export interface Tokens {
cache: Tokens.Cache;
input: number;
output: number;
reasoning: number;
}
export namespace Tokens {
export interface Cache {
read: number;
write: number;
}
}
}
export interface StepStartPart {
id: string;
messageID: string;
sessionID: string;
type: 'step-start';
}
export interface SymbolSource {
kind: number;
name: string;
path: string;
range: SymbolSource.Range;
text: FilePartSourceText;
type: 'symbol';
}
export namespace SymbolSource {
export interface Range {
end: Range.End;
start: Range.Start;
}
export namespace Range {
export interface End {
character: number;
line: number;
}
export interface Start {
character: number;
line: number;
}
}
}
export interface TextPart {
id: string;
messageID: string;
sessionID: string;
text: string;
type: 'text';
synthetic?: boolean;
time?: TextPart.Time;
}
export namespace TextPart {
export interface Time {
start: number;
end?: number;
}
}
export interface TextPartInput {
text: string;
type: 'text';
id?: string;
synthetic?: boolean;
time?: TextPartInput.Time;
}
export namespace TextPartInput {
export interface Time {
start: number;
end?: number;
}
}
export interface ToolPart {
id: string;
callID: string;
messageID: string;
sessionID: string;
state: ToolStatePending | ToolStateRunning | ToolStateCompleted | ToolStateError;
tool: string;
type: 'tool';
}
export interface ToolStateCompleted {
input: { [key: string]: unknown };
metadata: { [key: string]: unknown };
output: string;
status: 'completed';
time: ToolStateCompleted.Time;
title: string;
}
export namespace ToolStateCompleted {
export interface Time {
end: number;
start: number;
}
}
export interface ToolStateError {
error: string;
input: { [key: string]: unknown };
status: 'error';
time: ToolStateError.Time;
}
export namespace ToolStateError {
export interface Time {
end: number;
start: number;
}
}
export interface ToolStatePending {
status: 'pending';
}
export interface ToolStateRunning {
status: 'running';
time: ToolStateRunning.Time;
input?: unknown;
metadata?: { [key: string]: unknown };
title?: string;
}
export namespace ToolStateRunning {
export interface Time {
start: number;
}
}
export interface UserMessage {
id: string;
role: 'user';
sessionID: string;
time: UserMessage.Time;
}
export namespace UserMessage {
export interface Time {
created: number;
}
}
export type SessionListResponse = Array<Session>;
export type SessionDeleteResponse = boolean;
export type SessionAbortResponse = boolean;
export type SessionInitResponse = boolean;
export interface SessionMessageResponse {
info: Message;
parts: Array<Part>;
}
export type SessionMessagesResponse = Array<SessionMessagesResponse.SessionMessagesResponseItem>;
export namespace SessionMessagesResponse {
export interface SessionMessagesResponseItem {
info: SessionAPI.Message;
parts: Array<SessionAPI.Part>;
}
}
export type SessionSummarizeResponse = boolean;
export interface SessionChatParams {
modelID: string;
parts: Array<TextPartInput | FilePartInput>;
providerID: string;
messageID?: string;
mode?: string;
system?: string;
tools?: { [key: string]: boolean };
}
export interface SessionInitParams {
messageID: string;
modelID: string;
providerID: string;
}
export interface SessionMessageParams {
/**
* Session ID
*/
id: string;
}
export interface SessionRevertParams {
messageID: string;
partID?: string;
}
export interface SessionSummarizeParams {
modelID: string;
providerID: string;
}
SessionResource.Permissions = Permissions;
export declare namespace SessionResource {
export {
type AssistantMessage as AssistantMessage,
type FilePart as FilePart,
type FilePartInput as FilePartInput,
type FilePartSource as FilePartSource,
type FilePartSourceText as FilePartSourceText,
type FileSource as FileSource,
type Message as Message,
type Part as Part,
type Session as Session,
type SnapshotPart as SnapshotPart,
type StepFinishPart as StepFinishPart,
type StepStartPart as StepStartPart,
type SymbolSource as SymbolSource,
type TextPart as TextPart,
type TextPartInput as TextPartInput,
type ToolPart as ToolPart,
type ToolStateCompleted as ToolStateCompleted,
type ToolStateError as ToolStateError,
type ToolStatePending as ToolStatePending,
type ToolStateRunning as ToolStateRunning,
type UserMessage as UserMessage,
type SessionListResponse as SessionListResponse,
type SessionDeleteResponse as SessionDeleteResponse,
type SessionAbortResponse as SessionAbortResponse,
type SessionInitResponse as SessionInitResponse,
type SessionMessageResponse as SessionMessageResponse,
type SessionMessagesResponse as SessionMessagesResponse,
type SessionSummarizeResponse as SessionSummarizeResponse,
type SessionChatParams as SessionChatParams,
type SessionInitParams as SessionInitParams,
type SessionMessageParams as SessionMessageParams,
type SessionRevertParams as SessionRevertParams,
type SessionSummarizeParams as SessionSummarizeParams,
};
export {
Permissions as Permissions,
type Permission as Permission,
type PermissionRespondResponse as PermissionRespondResponse,
type PermissionRespondParams as PermissionRespondParams,
};
}

View File

@@ -118,11 +118,19 @@ resources:
share: post /session/{id}/share
unshare: delete /session/{id}/share
summarize: post /session/{id}/summarize
message: get /session/{id}/message/{messageID}
messages: get /session/{id}/message
chat: post /session/{id}/message
revert: post /session/{id}/revert
unrevert: post /session/{id}/unrevert
subresources:
permissions:
models:
permission: Permission
methods:
respond: post /session/{id}/permissions/{permissionID}
tui:
methods:
appendPrompt: post /tui/append-prompt

View File

@@ -0,0 +1,27 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import Opencode from '@opencode-ai/sdk';
const client = new Opencode({ baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010' });
describe('resource permissions', () => {
// skipped: tests are disabled for the time being
test.skip('respond: only required params', async () => {
const responsePromise = client.session.permissions.respond('permissionID', {
id: 'id',
response: 'once',
});
const rawResponse = await responsePromise.asResponse();
expect(rawResponse).toBeInstanceOf(Response);
const response = await responsePromise;
expect(response).not.toBeInstanceOf(Response);
const dataAndResponse = await responsePromise.withResponse();
expect(dataAndResponse.data).toBe(response);
expect(dataAndResponse.response).toBe(rawResponse);
});
// skipped: tests are disabled for the time being
test.skip('respond: required and optional params', async () => {
const response = await client.session.permissions.respond('permissionID', { id: 'id', response: 'once' });
});
});