zerg-ztc 0.1.11 → 0.1.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/ztc-audio-darwin-arm64 +0 -0
- package/dist/utils/dictation_native.d.ts.map +1 -1
- package/dist/utils/dictation_native.js +43 -23
- package/dist/utils/dictation_native.js.map +1 -1
- package/package.json +5 -4
- package/packages/ztc-dictation/Cargo.toml +0 -43
- package/packages/ztc-dictation/README.md +0 -65
- package/packages/ztc-dictation/index.d.ts +0 -16
- package/packages/ztc-dictation/index.js +0 -74
- package/packages/ztc-dictation/package.json +0 -41
- package/packages/ztc-dictation/src/main.rs +0 -430
- package/src/App.tsx +0 -910
- package/src/agent/agent.ts +0 -534
- package/src/agent/backends/anthropic.ts +0 -86
- package/src/agent/backends/gemini.ts +0 -119
- package/src/agent/backends/inception.ts +0 -23
- package/src/agent/backends/index.ts +0 -17
- package/src/agent/backends/openai.ts +0 -23
- package/src/agent/backends/openai_compatible.ts +0 -143
- package/src/agent/backends/types.ts +0 -83
- package/src/agent/commands/clipboard.ts +0 -77
- package/src/agent/commands/config.ts +0 -204
- package/src/agent/commands/debug.ts +0 -23
- package/src/agent/commands/dictation.ts +0 -11
- package/src/agent/commands/emulation.ts +0 -80
- package/src/agent/commands/execution.ts +0 -9
- package/src/agent/commands/help.ts +0 -20
- package/src/agent/commands/history.ts +0 -13
- package/src/agent/commands/index.ts +0 -48
- package/src/agent/commands/input_mode.ts +0 -22
- package/src/agent/commands/keybindings.ts +0 -40
- package/src/agent/commands/model.ts +0 -11
- package/src/agent/commands/models.ts +0 -116
- package/src/agent/commands/permissions.ts +0 -64
- package/src/agent/commands/retry.ts +0 -9
- package/src/agent/commands/shell.ts +0 -68
- package/src/agent/commands/skills.ts +0 -54
- package/src/agent/commands/status.ts +0 -19
- package/src/agent/commands/types.ts +0 -88
- package/src/agent/commands/update.ts +0 -32
- package/src/agent/factory.ts +0 -60
- package/src/agent/index.ts +0 -20
- package/src/agent/runtime/capabilities.ts +0 -7
- package/src/agent/runtime/memory.ts +0 -23
- package/src/agent/runtime/policy.ts +0 -48
- package/src/agent/runtime/session.ts +0 -18
- package/src/agent/runtime/tracing.ts +0 -23
- package/src/agent/tools/file.ts +0 -178
- package/src/agent/tools/index.ts +0 -52
- package/src/agent/tools/screenshot.ts +0 -821
- package/src/agent/tools/search.ts +0 -138
- package/src/agent/tools/shell.ts +0 -69
- package/src/agent/tools/skills.ts +0 -28
- package/src/agent/tools/types.ts +0 -14
- package/src/agent/tools/zerg.ts +0 -50
- package/src/cli.tsx +0 -163
- package/src/components/ActivityLine.tsx +0 -23
- package/src/components/FullScreen.tsx +0 -79
- package/src/components/Header.tsx +0 -27
- package/src/components/InputArea.tsx +0 -1660
- package/src/components/MessageList.tsx +0 -71
- package/src/components/SingleMessage.tsx +0 -298
- package/src/components/StatusBar.tsx +0 -55
- package/src/components/index.tsx +0 -8
- package/src/config/types.ts +0 -19
- package/src/config.ts +0 -186
- package/src/debug/logger.ts +0 -14
- package/src/emulation/README.md +0 -24
- package/src/emulation/catalog.ts +0 -82
- package/src/emulation/trace_style.ts +0 -8
- package/src/emulation/types.ts +0 -7
- package/src/skills/index.ts +0 -36
- package/src/skills/loader.ts +0 -135
- package/src/skills/registry.ts +0 -6
- package/src/skills/types.ts +0 -10
- package/src/types.ts +0 -84
- package/src/ui/README.md +0 -44
- package/src/ui/core/factory.ts +0 -9
- package/src/ui/core/index.ts +0 -4
- package/src/ui/core/input.ts +0 -38
- package/src/ui/core/input_segments.ts +0 -410
- package/src/ui/core/input_state.ts +0 -17
- package/src/ui/core/layout_yoga.ts +0 -122
- package/src/ui/core/style.ts +0 -38
- package/src/ui/core/types.ts +0 -54
- package/src/ui/ink/index.tsx +0 -1
- package/src/ui/ink/render.tsx +0 -60
- package/src/ui/views/activity_line.ts +0 -33
- package/src/ui/views/app.ts +0 -111
- package/src/ui/views/header.ts +0 -44
- package/src/ui/views/input_area.ts +0 -255
- package/src/ui/views/message_list.ts +0 -443
- package/src/ui/views/status_bar.ts +0 -114
- package/src/ui/vue/index.ts +0 -53
- package/src/ui/web/frame_render.tsx +0 -148
- package/src/ui/web/index.tsx +0 -1
- package/src/ui/web/render.tsx +0 -41
- package/src/utils/clipboard.ts +0 -39
- package/src/utils/clipboard_image.ts +0 -40
- package/src/utils/dictation.ts +0 -467
- package/src/utils/dictation_native.ts +0 -258
- package/src/utils/diff.ts +0 -52
- package/src/utils/image_preview.ts +0 -36
- package/src/utils/models.ts +0 -98
- package/src/utils/path_complete.ts +0 -173
- package/src/utils/path_format.ts +0 -99
- package/src/utils/shell.ts +0 -72
- package/src/utils/spinner_frames.ts +0 -1
- package/src/utils/spinner_verbs.ts +0 -23
- package/src/utils/table.ts +0 -171
- package/src/utils/tool_summary.ts +0 -56
- package/src/utils/tool_trace.ts +0 -346
- package/src/utils/update.ts +0 -44
- package/src/utils/version.ts +0 -15
- package/src/web/index.html +0 -352
- package/src/web/mirror-favicon.svg +0 -4
- package/src/web/mirror.html +0 -641
- package/src/web/mirror_hook.ts +0 -25
- package/src/web/mirror_server.ts +0 -204
- package/tsconfig.json +0 -22
- package/vite.config.ts +0 -363
- /package/{packages/ztc-dictation/bin → bin}/.gitkeep +0 -0
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
import { AgentBackend, BackendRequest, BackendResponse, ContentBlock } from './types.js';
|
|
2
|
-
|
|
3
|
-
interface GeminiConfig {
|
|
4
|
-
apiKey: string;
|
|
5
|
-
apiEndpoint?: string;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
interface GeminiCandidate {
|
|
9
|
-
content?: {
|
|
10
|
-
parts?: Array<{
|
|
11
|
-
text?: string;
|
|
12
|
-
functionCall?: {
|
|
13
|
-
name?: string;
|
|
14
|
-
args?: Record<string, unknown>;
|
|
15
|
-
};
|
|
16
|
-
}>;
|
|
17
|
-
};
|
|
18
|
-
finishReason?: string;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
interface GeminiResponse {
|
|
22
|
-
candidates?: GeminiCandidate[];
|
|
23
|
-
usageMetadata?: {
|
|
24
|
-
promptTokenCount?: number;
|
|
25
|
-
candidatesTokenCount?: number;
|
|
26
|
-
totalTokenCount?: number;
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export class GeminiBackend implements AgentBackend {
|
|
31
|
-
private apiKey: string;
|
|
32
|
-
private apiEndpoint: string;
|
|
33
|
-
|
|
34
|
-
constructor(config: GeminiConfig) {
|
|
35
|
-
this.apiKey = config.apiKey;
|
|
36
|
-
this.apiEndpoint = config.apiEndpoint || 'https://generativelanguage.googleapis.com/v1beta/models';
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
async generate(request: BackendRequest): Promise<BackendResponse> {
|
|
40
|
-
const url = `${this.apiEndpoint}/${encodeURIComponent(request.model)}:generateContent?key=${this.apiKey}`;
|
|
41
|
-
const mapParts = (content: BackendRequest['messages'][number]['content']) => {
|
|
42
|
-
if (typeof content === 'string') {
|
|
43
|
-
return [{ text: content }];
|
|
44
|
-
}
|
|
45
|
-
return content.map(block => {
|
|
46
|
-
if (block.type === 'text') {
|
|
47
|
-
return { text: block.text };
|
|
48
|
-
}
|
|
49
|
-
if (block.type === 'tool_result') {
|
|
50
|
-
// Gemini handles function responses differently - convert to text for now
|
|
51
|
-
const resultText = typeof block.content === 'string'
|
|
52
|
-
? block.content
|
|
53
|
-
: block.content.map(b => b.type === 'text' ? b.text : '[image]').join('\n');
|
|
54
|
-
return { text: `Function result: ${resultText}` };
|
|
55
|
-
}
|
|
56
|
-
if (block.type === 'tool_use') {
|
|
57
|
-
// Convert tool_use to function call format for Gemini
|
|
58
|
-
return { functionCall: { name: block.name, args: block.input } };
|
|
59
|
-
}
|
|
60
|
-
// Image block
|
|
61
|
-
return { inlineData: { mimeType: block.mediaType, data: block.data } };
|
|
62
|
-
});
|
|
63
|
-
};
|
|
64
|
-
const body = {
|
|
65
|
-
systemInstruction: { parts: [{ text: request.system }] },
|
|
66
|
-
contents: request.messages.map(m => ({
|
|
67
|
-
role: m.role === 'assistant' ? 'model' : 'user',
|
|
68
|
-
parts: mapParts(m.content)
|
|
69
|
-
})),
|
|
70
|
-
tools: [{
|
|
71
|
-
functionDeclarations: request.tools.map(t => ({
|
|
72
|
-
name: t.name,
|
|
73
|
-
description: t.description,
|
|
74
|
-
parameters: t.parameters
|
|
75
|
-
}))
|
|
76
|
-
}]
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
const response = await fetch(url, {
|
|
80
|
-
method: 'POST',
|
|
81
|
-
headers: { 'Content-Type': 'application/json' },
|
|
82
|
-
body: JSON.stringify(body)
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
if (!response.ok) {
|
|
86
|
-
const errorText = await response.text();
|
|
87
|
-
throw new Error(`API error (${response.status}): ${errorText}`);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const data = await response.json() as GeminiResponse;
|
|
91
|
-
const candidate = data.candidates?.[0];
|
|
92
|
-
const blocks: ContentBlock[] = [];
|
|
93
|
-
const parts = candidate?.content?.parts || [];
|
|
94
|
-
|
|
95
|
-
for (const part of parts) {
|
|
96
|
-
if (part.text) {
|
|
97
|
-
blocks.push({ type: 'text', text: part.text });
|
|
98
|
-
}
|
|
99
|
-
if (part.functionCall?.name) {
|
|
100
|
-
blocks.push({
|
|
101
|
-
type: 'tool_use',
|
|
102
|
-
id: `${part.functionCall.name}_${Date.now()}`,
|
|
103
|
-
name: part.functionCall.name,
|
|
104
|
-
input: part.functionCall.args || {}
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const inputTokens = data.usageMetadata?.promptTokenCount ?? 0;
|
|
110
|
-
const outputTokens = data.usageMetadata?.candidatesTokenCount ?? 0;
|
|
111
|
-
const totalTokens = data.usageMetadata?.totalTokenCount ?? (inputTokens + outputTokens);
|
|
112
|
-
|
|
113
|
-
return {
|
|
114
|
-
content: blocks,
|
|
115
|
-
stopReason: candidate?.finishReason || 'unknown',
|
|
116
|
-
usage: data.usageMetadata ? { inputTokens, outputTokens, totalTokens } : undefined
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { OpenAICompatibleBackend } from './openai_compatible.js';
|
|
2
|
-
import { AgentBackend, BackendRequest, BackendResponse } from './types.js';
|
|
3
|
-
|
|
4
|
-
interface InceptionConfig {
|
|
5
|
-
apiKey: string;
|
|
6
|
-
apiEndpoint?: string;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export class InceptionBackend implements AgentBackend {
|
|
10
|
-
private backend: OpenAICompatibleBackend;
|
|
11
|
-
|
|
12
|
-
constructor(config: InceptionConfig) {
|
|
13
|
-
this.backend = new OpenAICompatibleBackend({
|
|
14
|
-
apiKey: config.apiKey,
|
|
15
|
-
baseUrl: config.apiEndpoint || 'https://api.inceptionlabs.ai/v1',
|
|
16
|
-
apiPath: '/chat/completions'
|
|
17
|
-
});
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
async generate(request: BackendRequest): Promise<BackendResponse> {
|
|
21
|
-
return this.backend.generate(request);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
export { AnthropicBackend } from './anthropic.js';
|
|
2
|
-
export { OpenAIBackend } from './openai.js';
|
|
3
|
-
export { OpenAICompatibleBackend } from './openai_compatible.js';
|
|
4
|
-
export { InceptionBackend } from './inception.js';
|
|
5
|
-
export { GeminiBackend } from './gemini.js';
|
|
6
|
-
export type {
|
|
7
|
-
AgentBackend,
|
|
8
|
-
BackendRequest,
|
|
9
|
-
BackendResponse,
|
|
10
|
-
ContentBlock,
|
|
11
|
-
TextBlock,
|
|
12
|
-
ToolUseBlock,
|
|
13
|
-
LlmMessage,
|
|
14
|
-
RequestContentBlock,
|
|
15
|
-
ToolResultBlock,
|
|
16
|
-
TokenUsage
|
|
17
|
-
} from './types.js';
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { OpenAICompatibleBackend } from './openai_compatible.js';
|
|
2
|
-
import { AgentBackend, BackendRequest, BackendResponse } from './types.js';
|
|
3
|
-
|
|
4
|
-
interface OpenAIConfig {
|
|
5
|
-
apiKey: string;
|
|
6
|
-
apiEndpoint?: string;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export class OpenAIBackend implements AgentBackend {
|
|
10
|
-
private backend: OpenAICompatibleBackend;
|
|
11
|
-
|
|
12
|
-
constructor(config: OpenAIConfig) {
|
|
13
|
-
this.backend = new OpenAICompatibleBackend({
|
|
14
|
-
apiKey: config.apiKey,
|
|
15
|
-
baseUrl: config.apiEndpoint || 'https://api.openai.com/v1',
|
|
16
|
-
apiPath: '/chat/completions'
|
|
17
|
-
});
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
async generate(request: BackendRequest): Promise<BackendResponse> {
|
|
21
|
-
return this.backend.generate(request);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
import { AgentBackend, BackendRequest, BackendResponse, ContentBlock } from './types.js';
|
|
2
|
-
|
|
3
|
-
interface OpenAICompatibleConfig {
|
|
4
|
-
apiKey: string;
|
|
5
|
-
baseUrl: string;
|
|
6
|
-
apiPath?: string;
|
|
7
|
-
headers?: Record<string, string>;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
interface OpenAICompatibleResponse {
|
|
11
|
-
choices?: Array<{
|
|
12
|
-
message?: {
|
|
13
|
-
content?: string | null;
|
|
14
|
-
tool_calls?: Array<{
|
|
15
|
-
id?: string;
|
|
16
|
-
function?: {
|
|
17
|
-
name?: string;
|
|
18
|
-
arguments?: string;
|
|
19
|
-
};
|
|
20
|
-
}>;
|
|
21
|
-
};
|
|
22
|
-
finish_reason?: string;
|
|
23
|
-
}>;
|
|
24
|
-
usage?: {
|
|
25
|
-
prompt_tokens?: number;
|
|
26
|
-
completion_tokens?: number;
|
|
27
|
-
total_tokens?: number;
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export class OpenAICompatibleBackend implements AgentBackend {
|
|
32
|
-
private apiKey: string;
|
|
33
|
-
private baseUrl: string;
|
|
34
|
-
private apiPath: string;
|
|
35
|
-
private headers: Record<string, string>;
|
|
36
|
-
|
|
37
|
-
constructor(config: OpenAICompatibleConfig) {
|
|
38
|
-
this.apiKey = config.apiKey;
|
|
39
|
-
this.baseUrl = config.baseUrl.replace(/\/+$/, '');
|
|
40
|
-
this.apiPath = config.apiPath || '/chat/completions';
|
|
41
|
-
this.headers = config.headers || {};
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
async generate(request: BackendRequest): Promise<BackendResponse> {
|
|
45
|
-
const url = `${this.baseUrl}${this.apiPath}`;
|
|
46
|
-
const mapContent = (content: BackendRequest['messages'][number]['content']) => {
|
|
47
|
-
if (typeof content === 'string') return content;
|
|
48
|
-
return content.map(block => {
|
|
49
|
-
if (block.type === 'text') {
|
|
50
|
-
return { type: 'text', text: block.text };
|
|
51
|
-
}
|
|
52
|
-
if (block.type === 'tool_result') {
|
|
53
|
-
// OpenAI format: convert tool result to text
|
|
54
|
-
const resultText = typeof block.content === 'string'
|
|
55
|
-
? block.content
|
|
56
|
-
: block.content.map(b => b.type === 'text' ? b.text : '[image]').join('\n');
|
|
57
|
-
return { type: 'text', text: `Tool result (${block.tool_use_id}): ${resultText}` };
|
|
58
|
-
}
|
|
59
|
-
if (block.type === 'tool_use') {
|
|
60
|
-
// OpenAI handles tool calls differently - convert to text representation
|
|
61
|
-
return { type: 'text', text: `[Tool call: ${block.name}(${JSON.stringify(block.input)})]` };
|
|
62
|
-
}
|
|
63
|
-
// Image block
|
|
64
|
-
return {
|
|
65
|
-
type: 'image_url',
|
|
66
|
-
image_url: { url: `data:${block.mediaType};base64,${block.data}` }
|
|
67
|
-
};
|
|
68
|
-
});
|
|
69
|
-
};
|
|
70
|
-
const body = {
|
|
71
|
-
model: request.model,
|
|
72
|
-
max_tokens: request.maxTokens,
|
|
73
|
-
messages: [
|
|
74
|
-
{ role: 'system', content: request.system },
|
|
75
|
-
...request.messages.map(message => ({
|
|
76
|
-
role: message.role,
|
|
77
|
-
content: mapContent(message.content)
|
|
78
|
-
}))
|
|
79
|
-
],
|
|
80
|
-
tools: request.tools.map(t => ({
|
|
81
|
-
type: 'function',
|
|
82
|
-
function: {
|
|
83
|
-
name: t.name,
|
|
84
|
-
description: t.description,
|
|
85
|
-
parameters: t.parameters
|
|
86
|
-
}
|
|
87
|
-
}))
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
const response = await fetch(url, {
|
|
91
|
-
method: 'POST',
|
|
92
|
-
headers: {
|
|
93
|
-
'Content-Type': 'application/json',
|
|
94
|
-
Authorization: `Bearer ${this.apiKey}`,
|
|
95
|
-
...this.headers
|
|
96
|
-
},
|
|
97
|
-
body: JSON.stringify(body)
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
if (!response.ok) {
|
|
101
|
-
const errorText = await response.text();
|
|
102
|
-
throw new Error(`API error (${response.status}): ${errorText}`);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const data = await response.json() as OpenAICompatibleResponse;
|
|
106
|
-
const choice = data.choices?.[0];
|
|
107
|
-
const message = choice?.message;
|
|
108
|
-
const blocks: ContentBlock[] = [];
|
|
109
|
-
|
|
110
|
-
if (message?.content) {
|
|
111
|
-
blocks.push({ type: 'text', text: message.content });
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
if (message?.tool_calls) {
|
|
115
|
-
for (const call of message.tool_calls) {
|
|
116
|
-
const name = call.function?.name || 'unknown_tool';
|
|
117
|
-
const rawArgs = call.function?.arguments || '{}';
|
|
118
|
-
let args: Record<string, unknown> = {};
|
|
119
|
-
try {
|
|
120
|
-
args = JSON.parse(rawArgs);
|
|
121
|
-
} catch {
|
|
122
|
-
args = { _raw: rawArgs };
|
|
123
|
-
}
|
|
124
|
-
blocks.push({
|
|
125
|
-
type: 'tool_use',
|
|
126
|
-
id: call.id || `${name}_${Date.now()}`,
|
|
127
|
-
name,
|
|
128
|
-
input: args
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
const inputTokens = data.usage?.prompt_tokens ?? 0;
|
|
134
|
-
const outputTokens = data.usage?.completion_tokens ?? 0;
|
|
135
|
-
const totalTokens = data.usage?.total_tokens ?? (inputTokens + outputTokens);
|
|
136
|
-
|
|
137
|
-
return {
|
|
138
|
-
content: blocks,
|
|
139
|
-
stopReason: choice?.finish_reason || 'unknown',
|
|
140
|
-
usage: data.usage ? { inputTokens, outputTokens, totalTokens } : undefined
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
}
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import { ToolDefinition } from '../../types.js';
|
|
2
|
-
|
|
3
|
-
export interface RequestTextBlock {
|
|
4
|
-
type: 'text';
|
|
5
|
-
text: string;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export interface RequestImageBlock {
|
|
9
|
-
type: 'image';
|
|
10
|
-
mediaType: string;
|
|
11
|
-
data: string;
|
|
12
|
-
path?: string;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface ToolResultImageSource {
|
|
16
|
-
type: 'base64';
|
|
17
|
-
media_type: string;
|
|
18
|
-
data: string;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export interface ToolResultImageBlock {
|
|
22
|
-
type: 'image';
|
|
23
|
-
source: ToolResultImageSource;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export interface ToolResultBlock {
|
|
27
|
-
type: 'tool_result';
|
|
28
|
-
tool_use_id: string;
|
|
29
|
-
content: string | Array<RequestTextBlock | ToolResultImageBlock>;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export interface ToolUseRequestBlock {
|
|
33
|
-
type: 'tool_use';
|
|
34
|
-
id: string;
|
|
35
|
-
name: string;
|
|
36
|
-
input: Record<string, unknown>;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export type RequestContentBlock = RequestTextBlock | RequestImageBlock | ToolResultBlock | ToolUseRequestBlock;
|
|
40
|
-
|
|
41
|
-
export interface LlmMessage {
|
|
42
|
-
role: 'user' | 'assistant';
|
|
43
|
-
content: string | RequestContentBlock[];
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export interface TextBlock {
|
|
47
|
-
type: 'text';
|
|
48
|
-
text: string;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export interface ToolUseBlock {
|
|
52
|
-
type: 'tool_use';
|
|
53
|
-
id: string;
|
|
54
|
-
name: string;
|
|
55
|
-
input: Record<string, unknown>;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export type ContentBlock = TextBlock | ToolUseBlock;
|
|
59
|
-
|
|
60
|
-
export interface BackendRequest {
|
|
61
|
-
model: string;
|
|
62
|
-
maxTokens: number;
|
|
63
|
-
system: string;
|
|
64
|
-
messages: LlmMessage[];
|
|
65
|
-
tools: ToolDefinition[];
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export interface BackendResponse {
|
|
69
|
-
content: ContentBlock[];
|
|
70
|
-
stopReason: string;
|
|
71
|
-
usage?: TokenUsage;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export interface TokenUsage {
|
|
75
|
-
inputTokens: number;
|
|
76
|
-
outputTokens: number;
|
|
77
|
-
totalTokens: number;
|
|
78
|
-
estimated?: boolean;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export interface AgentBackend {
|
|
82
|
-
generate(request: BackendRequest): Promise<BackendResponse>;
|
|
83
|
-
}
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import { Command } from './types.js';
|
|
2
|
-
|
|
3
|
-
type Role = 'user' | 'assistant' | 'system';
|
|
4
|
-
|
|
5
|
-
function formatMessages(messages: { role: string; content: string }[]): string {
|
|
6
|
-
return messages
|
|
7
|
-
.map(msg => `[${msg.role}] ${msg.content}`)
|
|
8
|
-
.join('\n\n')
|
|
9
|
-
.trim();
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
function lastByRole(messages: { role: string; content: string }[], role: Role): { role: string; content: string } | undefined {
|
|
13
|
-
for (let i = messages.length - 1; i >= 0; i -= 1) {
|
|
14
|
-
if (messages[i].role === role) return messages[i];
|
|
15
|
-
}
|
|
16
|
-
return undefined;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export const clipboardCommand: Command = {
|
|
20
|
-
name: 'clipboard',
|
|
21
|
-
description: 'Copy conversation text to the clipboard',
|
|
22
|
-
usage: '<all|last|assistant|user|system> [role]',
|
|
23
|
-
handler: async (args, ctx) => {
|
|
24
|
-
const messages = ctx.getMessages();
|
|
25
|
-
if (messages.length === 0) {
|
|
26
|
-
ctx.addMessage({ role: 'system', content: 'Clipboard: no messages to copy.' });
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const target = (args[0] || 'last').toLowerCase();
|
|
31
|
-
const roleArg = (args[1] || '').toLowerCase();
|
|
32
|
-
const role = (roleArg === 'assistant' || roleArg === 'user' || roleArg === 'system')
|
|
33
|
-
? roleArg as Role
|
|
34
|
-
: undefined;
|
|
35
|
-
|
|
36
|
-
let text = '';
|
|
37
|
-
let label = target;
|
|
38
|
-
|
|
39
|
-
if (target === 'all') {
|
|
40
|
-
text = formatMessages(messages);
|
|
41
|
-
} else if (target === 'last') {
|
|
42
|
-
const msg = role ? lastByRole(messages, role) : messages[messages.length - 1];
|
|
43
|
-
if (!msg) {
|
|
44
|
-
ctx.addMessage({ role: 'system', content: `Clipboard: no ${role} messages found.` });
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
text = formatMessages([msg]);
|
|
48
|
-
label = role ? `last ${role}` : 'last';
|
|
49
|
-
} else if (target === 'assistant' || target === 'user' || target === 'system') {
|
|
50
|
-
const msg = lastByRole(messages, target as Role);
|
|
51
|
-
if (!msg) {
|
|
52
|
-
ctx.addMessage({ role: 'system', content: `Clipboard: no ${target} messages found.` });
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
text = formatMessages([msg]);
|
|
56
|
-
} else {
|
|
57
|
-
ctx.addMessage({
|
|
58
|
-
role: 'system',
|
|
59
|
-
content: 'Usage: /clipboard <all|last|assistant|user|system> [role]\n\nExamples:\n /clipboard all\n /clipboard last\n /clipboard last assistant\n /clipboard assistant'
|
|
60
|
-
});
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if (!text) {
|
|
65
|
-
ctx.addMessage({ role: 'system', content: 'Clipboard: nothing to copy.' });
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
try {
|
|
70
|
-
await ctx.clipboard.writeText(text);
|
|
71
|
-
ctx.addMessage({ role: 'system', content: `✓ Copied ${text.length} chars (${label})` });
|
|
72
|
-
} catch (err) {
|
|
73
|
-
const message = err instanceof Error ? err.message : 'Failed to copy';
|
|
74
|
-
ctx.addMessage({ role: 'system', content: `Clipboard error: ${message}` });
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
};
|
|
@@ -1,204 +0,0 @@
|
|
|
1
|
-
import { Command } from './types.js';
|
|
2
|
-
import { DEFAULT_SPINNER_VERBS, formatSpinnerVerbs, parseSpinnerVerbs } from '../../utils/spinner_verbs.js';
|
|
3
|
-
|
|
4
|
-
export const configCommand: Command = {
|
|
5
|
-
name: 'config',
|
|
6
|
-
description: 'Manage configuration',
|
|
7
|
-
usage: '<show|key|provider|endpoint|model|spinner> [value]',
|
|
8
|
-
handler: async (args, ctx) => {
|
|
9
|
-
const [subCmd, ...rest] = args;
|
|
10
|
-
const value = rest.join(' ');
|
|
11
|
-
|
|
12
|
-
switch (subCmd) {
|
|
13
|
-
case 'show':
|
|
14
|
-
if (ctx.config.refresh) {
|
|
15
|
-
await ctx.config.refresh();
|
|
16
|
-
}
|
|
17
|
-
const config = ctx.config.get();
|
|
18
|
-
ctx.addMessage({
|
|
19
|
-
role: 'system',
|
|
20
|
-
content: [
|
|
21
|
-
'Current configuration:',
|
|
22
|
-
` API Key: ${ctx.config.getMaskedApiKey()}`,
|
|
23
|
-
` Provider: ${ctx.config.getProvider()}`,
|
|
24
|
-
` Model: ${config.model}`,
|
|
25
|
-
` Max Tokens: ${config.maxTokens}`,
|
|
26
|
-
` Zerg Endpoint: ${config.zergEndpoint || '(not set)'}`,
|
|
27
|
-
` Emulation: ${ctx.config.getEmulationId() || '(none)'}`,
|
|
28
|
-
` Spinner verbs: ${formatSpinnerVerbs(config.spinnerVerbs || DEFAULT_SPINNER_VERBS)}`,
|
|
29
|
-
'',
|
|
30
|
-
`Config storage: ${ctx.config.locationLabel || '~/.ztc/config.json'}`
|
|
31
|
-
].join('\n')
|
|
32
|
-
});
|
|
33
|
-
break;
|
|
34
|
-
|
|
35
|
-
case 'key':
|
|
36
|
-
if (!value) {
|
|
37
|
-
ctx.addMessage({
|
|
38
|
-
role: 'system',
|
|
39
|
-
content: `Current API key: ${ctx.config.getMaskedApiKey()}\n\nUsage: /config key <your-api-key>`
|
|
40
|
-
});
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
let provider = ctx.config.getProvider();
|
|
45
|
-
let apiKey = value;
|
|
46
|
-
if (args[0] && ['anthropic', 'openai', 'gemini', 'inception', 'openai_compatible'].includes(args[0])) {
|
|
47
|
-
provider = args[0];
|
|
48
|
-
apiKey = args.slice(1).join(' ');
|
|
49
|
-
}
|
|
50
|
-
if (!apiKey) {
|
|
51
|
-
ctx.addMessage({
|
|
52
|
-
role: 'system',
|
|
53
|
-
content: 'Usage: /config key <provider> <api-key>'
|
|
54
|
-
});
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
if (provider === 'anthropic' && !apiKey.startsWith('sk-ant-')) {
|
|
59
|
-
ctx.addMessage({
|
|
60
|
-
role: 'system',
|
|
61
|
-
content: '⚠️ Warning: API key doesn\'t look like an Anthropic key (should start with sk-ant-). Setting anyway...'
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
ctx.config.setApiKey(apiKey, provider);
|
|
66
|
-
ctx.config.save();
|
|
67
|
-
ctx.reloadAgent();
|
|
68
|
-
ctx.addMessage({
|
|
69
|
-
role: 'system',
|
|
70
|
-
content: `✓ API key updated (${provider}): ${ctx.config.getMaskedApiKey()}\n Saved to ${ctx.config.locationLabel || '~/.ztc/config.json'}`
|
|
71
|
-
});
|
|
72
|
-
break;
|
|
73
|
-
|
|
74
|
-
case 'provider':
|
|
75
|
-
if (!value) {
|
|
76
|
-
ctx.addMessage({
|
|
77
|
-
role: 'system',
|
|
78
|
-
content: `Current provider: ${ctx.config.getProvider()}\n\nUsage: /config provider <anthropic|openai|gemini|inception|openai_compatible>`
|
|
79
|
-
});
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
ctx.config.setProvider(value);
|
|
83
|
-
ctx.config.save();
|
|
84
|
-
ctx.reloadAgent();
|
|
85
|
-
ctx.addMessage({
|
|
86
|
-
role: 'system',
|
|
87
|
-
content: `✓ Provider updated: ${value}`
|
|
88
|
-
});
|
|
89
|
-
break;
|
|
90
|
-
|
|
91
|
-
case 'endpoint':
|
|
92
|
-
if (!value) {
|
|
93
|
-
ctx.addMessage({
|
|
94
|
-
role: 'system',
|
|
95
|
-
content: `Current OpenAI-compatible base URL: ${ctx.config.getOpenAICompatibleBaseUrl() || '(not set)'}\n\nUsage: /config endpoint <url>`
|
|
96
|
-
});
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
ctx.config.setOpenAICompatibleBaseUrl(value);
|
|
100
|
-
ctx.config.save();
|
|
101
|
-
ctx.reloadAgent();
|
|
102
|
-
ctx.addMessage({
|
|
103
|
-
role: 'system',
|
|
104
|
-
content: `✓ OpenAI-compatible base URL updated: ${value}`
|
|
105
|
-
});
|
|
106
|
-
break;
|
|
107
|
-
|
|
108
|
-
case 'model':
|
|
109
|
-
if (!value) {
|
|
110
|
-
ctx.addMessage({
|
|
111
|
-
role: 'system',
|
|
112
|
-
content: `Current model: ${ctx.config.get().model}\n\nUsage: /config model <model-name>\n\nAvailable models:\n claude-opus-4-20250514\n claude-sonnet-4-20250514\n claude-haiku-3-20240307`
|
|
113
|
-
});
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
ctx.config.set('model', value);
|
|
117
|
-
ctx.config.save();
|
|
118
|
-
ctx.reloadAgent();
|
|
119
|
-
ctx.addMessage({
|
|
120
|
-
role: 'system',
|
|
121
|
-
content: `✓ Model updated: ${value}`
|
|
122
|
-
});
|
|
123
|
-
break;
|
|
124
|
-
|
|
125
|
-
case 'spinner': {
|
|
126
|
-
const [action, ...restParts] = rest;
|
|
127
|
-
const actionValue = restParts.join(' ');
|
|
128
|
-
const current = ctx.config.get().spinnerVerbs || DEFAULT_SPINNER_VERBS;
|
|
129
|
-
|
|
130
|
-
if (!action || action === 'show') {
|
|
131
|
-
ctx.addMessage({
|
|
132
|
-
role: 'system',
|
|
133
|
-
content: `Spinner verbs:\n ${formatSpinnerVerbs(current)}\n\nUsage:\n /config spinner show\n /config spinner set <verb1, verb2, ...>\n /config spinner add <verb>\n /config spinner remove <verb>\n /config spinner reset\n /config spinner off`
|
|
134
|
-
});
|
|
135
|
-
return;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
if (action === 'reset') {
|
|
139
|
-
ctx.config.set('spinnerVerbs', DEFAULT_SPINNER_VERBS);
|
|
140
|
-
ctx.config.save();
|
|
141
|
-
ctx.addMessage({ role: 'system', content: '✓ Spinner verbs reset to defaults.' });
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
if (action === 'off') {
|
|
146
|
-
ctx.config.set('spinnerVerbs', []);
|
|
147
|
-
ctx.config.save();
|
|
148
|
-
ctx.addMessage({ role: 'system', content: '✓ Spinner verbs disabled.' });
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
if (action === 'set') {
|
|
153
|
-
const verbs = parseSpinnerVerbs(actionValue);
|
|
154
|
-
if (verbs.length === 0) {
|
|
155
|
-
ctx.addMessage({ role: 'system', content: 'Usage: /config spinner set <verb1, verb2, ...>' });
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
ctx.config.set('spinnerVerbs', verbs);
|
|
159
|
-
ctx.config.save();
|
|
160
|
-
ctx.addMessage({ role: 'system', content: `✓ Spinner verbs updated: ${formatSpinnerVerbs(verbs)}` });
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
if (action === 'add') {
|
|
165
|
-
const verbs = parseSpinnerVerbs(actionValue);
|
|
166
|
-
if (verbs.length === 0) {
|
|
167
|
-
ctx.addMessage({ role: 'system', content: 'Usage: /config spinner add <verb>' });
|
|
168
|
-
return;
|
|
169
|
-
}
|
|
170
|
-
const next = [...current, ...verbs];
|
|
171
|
-
ctx.config.set('spinnerVerbs', next);
|
|
172
|
-
ctx.config.save();
|
|
173
|
-
ctx.addMessage({ role: 'system', content: `✓ Added spinner verbs: ${formatSpinnerVerbs(verbs)}` });
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
if (action === 'remove') {
|
|
178
|
-
const verbs = parseSpinnerVerbs(actionValue).map(v => v.toLowerCase());
|
|
179
|
-
if (verbs.length === 0) {
|
|
180
|
-
ctx.addMessage({ role: 'system', content: 'Usage: /config spinner remove <verb>' });
|
|
181
|
-
return;
|
|
182
|
-
}
|
|
183
|
-
const next = current.filter(v => !verbs.includes(v.toLowerCase()));
|
|
184
|
-
ctx.config.set('spinnerVerbs', next);
|
|
185
|
-
ctx.config.save();
|
|
186
|
-
ctx.addMessage({ role: 'system', content: `✓ Removed spinner verbs: ${formatSpinnerVerbs(verbs)}` });
|
|
187
|
-
return;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
ctx.addMessage({
|
|
191
|
-
role: 'system',
|
|
192
|
-
content: 'Usage: /config spinner <show|set|add|remove|reset|off> [value]'
|
|
193
|
-
});
|
|
194
|
-
break;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
default:
|
|
198
|
-
ctx.addMessage({
|
|
199
|
-
role: 'system',
|
|
200
|
-
content: 'Usage: /config <show|key|provider|endpoint|model|spinner> [value]\n\nExamples:\n /config show Show current config\n /config key sk-ant-... Set API key (current provider)\n /config key openai sk-... Set API key for provider\n /config provider openai Set provider\n /config endpoint https://api.example.com/v1 Set OpenAI-compatible base URL\n /config model claude-opus-4-20250514 Set model\n /config spinner set Reticulating splines, Organizing thoughts'
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
};
|