@oh-my-pi/pi-coding-agent 6.8.4 → 6.9.0

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.
@@ -1,213 +0,0 @@
1
- import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
2
- import { StringEnum } from "@oh-my-pi/pi-ai";
3
- import { type GitParams, gitTool as gitToolCore, type ToolResponse } from "@oh-my-pi/pi-git-tool";
4
- import { type Static, Type } from "@sinclair/typebox";
5
- import gitDescription from "../../prompts/tools/git.md" with { type: "text" };
6
- import { renderPromptTemplate } from "../prompt-templates";
7
- import type { ToolSession } from "./index";
8
-
9
- const gitSchema = Type.Object({
10
- operation: StringEnum([
11
- "status",
12
- "diff",
13
- "log",
14
- "show",
15
- "blame",
16
- "branch",
17
- "add",
18
- "restore",
19
- "commit",
20
- "checkout",
21
- "merge",
22
- "rebase",
23
- "stash",
24
- "cherry-pick",
25
- "fetch",
26
- "pull",
27
- "push",
28
- "tag",
29
- "pr",
30
- "issue",
31
- "ci",
32
- "release",
33
- ]),
34
-
35
- // Status
36
- only: Type.Optional(StringEnum(["branch", "modified", "staged", "untracked", "conflicts", "sync"])),
37
- ignored: Type.Optional(Type.Boolean()),
38
-
39
- // Diff
40
- target: Type.Optional(
41
- Type.Union([
42
- Type.Object({
43
- from: Type.String(),
44
- to: Type.Optional(Type.String()),
45
- }),
46
- Type.String(),
47
- ]),
48
- ),
49
- paths: Type.Optional(Type.Array(Type.String())),
50
- stat_only: Type.Optional(Type.Boolean()),
51
- name_only: Type.Optional(Type.Boolean()),
52
- context: Type.Optional(Type.Number()),
53
- max_lines: Type.Optional(Type.Number()),
54
- ignore_whitespace: Type.Optional(Type.Boolean()),
55
-
56
- // Log
57
- limit: Type.Optional(Type.Number()),
58
- ref: Type.Optional(Type.String()),
59
- author: Type.Optional(Type.String()),
60
- since: Type.Optional(Type.String()),
61
- until: Type.Optional(Type.String()),
62
- grep: Type.Optional(Type.String()),
63
- format: Type.Optional(StringEnum(["oneline", "short", "full"])),
64
- stat: Type.Optional(Type.Boolean()),
65
- merges: Type.Optional(Type.Boolean()),
66
- first_parent: Type.Optional(Type.Boolean()),
67
-
68
- // Show
69
- path: Type.Optional(Type.String()),
70
- diff: Type.Optional(Type.Boolean()),
71
- lines: Type.Optional(
72
- Type.Object({
73
- start: Type.Number(),
74
- end: Type.Number(),
75
- }),
76
- ),
77
-
78
- // Blame
79
- root: Type.Optional(Type.Boolean()),
80
-
81
- // Branch
82
- action: Type.Optional(StringEnum(["list", "create", "delete", "rename", "current"])),
83
- name: Type.Optional(Type.String()),
84
- newName: Type.Optional(Type.String()),
85
- startPoint: Type.Optional(Type.String()),
86
- remotes: Type.Optional(Type.Boolean()),
87
- force: Type.Optional(Type.Boolean()),
88
-
89
- // Add/Restore
90
- update: Type.Optional(Type.Boolean()),
91
- all: Type.Optional(Type.Boolean()),
92
- dry_run: Type.Optional(Type.Boolean()),
93
- staged: Type.Optional(Type.Boolean()),
94
- worktree: Type.Optional(Type.Boolean()),
95
- source: Type.Optional(Type.String()),
96
-
97
- // Commit
98
- message: Type.Optional(Type.String()),
99
- allow_empty: Type.Optional(Type.Boolean()),
100
- sign: Type.Optional(Type.Boolean()),
101
- no_verify: Type.Optional(Type.Boolean()),
102
- amend: Type.Optional(Type.Boolean()),
103
-
104
- // Checkout
105
- create: Type.Optional(Type.Boolean()),
106
-
107
- // Merge
108
- no_ff: Type.Optional(Type.Boolean()),
109
- ff_only: Type.Optional(Type.Boolean()),
110
- squash: Type.Optional(Type.Boolean()),
111
- abort: Type.Optional(Type.Boolean()),
112
- continue: Type.Optional(Type.Boolean()),
113
-
114
- // Rebase
115
- onto: Type.Optional(Type.String()),
116
- upstream: Type.Optional(Type.String()),
117
- skip: Type.Optional(Type.Boolean()),
118
-
119
- // Stash
120
- include_untracked: Type.Optional(Type.Boolean()),
121
- index: Type.Optional(Type.Number()),
122
- keep_index: Type.Optional(Type.Boolean()),
123
-
124
- // Cherry-pick
125
- commits: Type.Optional(Type.Array(Type.String())),
126
- no_commit: Type.Optional(Type.Boolean()),
127
-
128
- // Fetch/Pull/Push/Tag
129
- remote: Type.Optional(Type.String()),
130
- branch: Type.Optional(Type.String()),
131
- prune: Type.Optional(Type.Boolean()),
132
- tags: Type.Optional(Type.Boolean()),
133
- rebase: Type.Optional(Type.Boolean()),
134
- set_upstream: Type.Optional(Type.Boolean()),
135
- force_with_lease: Type.Optional(Type.Boolean()),
136
- delete: Type.Optional(Type.Boolean()),
137
- force_override: Type.Optional(Type.Boolean()),
138
-
139
- // Tag
140
- // (name/message/ref already covered)
141
-
142
- // PR
143
- number: Type.Optional(Type.Number()),
144
- title: Type.Optional(Type.String()),
145
- body: Type.Optional(Type.String()),
146
- base: Type.Optional(Type.String()),
147
- head: Type.Optional(Type.String()),
148
- draft: Type.Optional(Type.Boolean()),
149
- state: Type.Optional(StringEnum(["open", "closed", "merged", "all"])),
150
- merge_method: Type.Optional(StringEnum(["merge", "squash", "rebase"])),
151
- review_action: Type.Optional(StringEnum(["approve", "request-changes", "comment"])),
152
- review_body: Type.Optional(Type.String()),
153
-
154
- // Issue
155
- labels: Type.Optional(Type.Array(Type.String())),
156
- assignee: Type.Optional(Type.String()),
157
- comment_body: Type.Optional(Type.String()),
158
-
159
- // CI
160
- workflow: Type.Optional(Type.String()),
161
- run_id: Type.Optional(Type.Number()),
162
- inputs: Type.Optional(Type.Record(Type.String(), Type.String())),
163
- logs_failed: Type.Optional(Type.Boolean()),
164
-
165
- // Release
166
- notes: Type.Optional(Type.String()),
167
- generate_notes: Type.Optional(Type.Boolean()),
168
- prerelease: Type.Optional(Type.Boolean()),
169
- assets: Type.Optional(Type.Array(Type.String())),
170
- });
171
-
172
- export type GitToolDetails = ToolResponse<unknown>;
173
-
174
- export class GitTool implements AgentTool<typeof gitSchema, GitToolDetails> {
175
- public readonly name = "git";
176
- public readonly label = "Git";
177
- public readonly description: string;
178
- public readonly parameters = gitSchema;
179
-
180
- private readonly session: ToolSession;
181
-
182
- constructor(session: ToolSession) {
183
- this.session = session;
184
- this.description = renderPromptTemplate(gitDescription);
185
- }
186
-
187
- static createIf(session: ToolSession): GitTool | null {
188
- return session.settings?.getGitToolEnabled() === false ? null : new GitTool(session);
189
- }
190
-
191
- public async execute(
192
- _toolCallId: string,
193
- params: Static<typeof gitSchema>,
194
- _signal?: AbortSignal,
195
- _onUpdate?: AgentToolUpdateCallback<GitToolDetails>,
196
- _context?: AgentToolContext,
197
- ): Promise<AgentToolResult<GitToolDetails>> {
198
- if (params.operation === "commit" && !params.message) {
199
- throw new Error("Git commit requires a message to avoid an interactive editor. Provide `message`.");
200
- }
201
-
202
- const result = await gitToolCore(params as GitParams, this.session.cwd);
203
- if ("error" in result) {
204
- const message = result._rendered ?? result.error;
205
- return { content: [{ type: "text", text: message }], details: result };
206
- }
207
- if ("confirm" in result) {
208
- const message = result._rendered ?? result.confirm;
209
- return { content: [{ type: "text", text: message }], details: result };
210
- }
211
- return { content: [{ type: "text", text: result._rendered }], details: result };
212
- }
213
- }
@@ -1,135 +0,0 @@
1
- import { logger } from "@oh-my-pi/pi-utils";
2
- import { Agent, run, setDefaultOpenAIKey } from "@openai/agents";
3
- import { z } from "zod";
4
- import type { ModelRegistry } from "./model-registry";
5
-
6
- const DEFAULT_CONTROLLER_MODEL = process.env.OMP_VOICE_CONTROLLER_MODEL ?? "gpt-4o-mini";
7
- const DEFAULT_SUMMARY_MODEL = process.env.OMP_VOICE_SUMMARY_MODEL ?? DEFAULT_CONTROLLER_MODEL;
8
- const MAX_INPUT_CHARS = 8000;
9
-
10
- export type VoiceSteeringDecision = { action: "pass" | "ask"; text: string };
11
- export type VoicePresentationDecision = { action: "skip" | "speak"; text?: string };
12
- type VoiceSummaryOutput = { text: string };
13
-
14
- const steeringSchema: z.ZodType<VoiceSteeringDecision> = z.object({
15
- action: z.enum(["pass", "ask"]),
16
- text: z.string().min(1),
17
- });
18
-
19
- const presentationSchema: z.ZodType<VoicePresentationDecision> = z.object({
20
- action: z.enum(["skip", "speak"]),
21
- text: z.string().min(1).optional(),
22
- });
23
-
24
- const summarySchema: z.ZodType<VoiceSummaryOutput> = z.object({
25
- text: z.string().min(1),
26
- });
27
-
28
- function normalizeText(text: string): string {
29
- return text.replace(/\s+/g, " ").trim();
30
- }
31
-
32
- function truncateText(text: string, maxChars: number): string {
33
- if (text.length <= maxChars) return text;
34
- return `${text.slice(0, maxChars)}...`;
35
- }
36
-
37
- export class VoiceController {
38
- private lastApiKey: string | undefined;
39
-
40
- constructor(private registry: ModelRegistry) {}
41
-
42
- private async ensureApiKey(): Promise<string | null> {
43
- const apiKey = await this.registry.getApiKeyForProvider("openai");
44
- if (!apiKey) {
45
- logger.debug("voice-controller: no OpenAI API key available");
46
- return null;
47
- }
48
- if (apiKey !== this.lastApiKey) {
49
- setDefaultOpenAIKey(apiKey);
50
- this.lastApiKey = apiKey;
51
- }
52
- return apiKey;
53
- }
54
-
55
- async steerUserInput(text: string): Promise<VoiceSteeringDecision | null> {
56
- if (!(await this.ensureApiKey())) return null;
57
-
58
- const normalized = truncateText(normalizeText(text), MAX_INPUT_CHARS);
59
- const agent = new Agent({
60
- name: "Voice Input Steering",
61
- instructions:
62
- "You are a voice-input controller for a coding agent. " +
63
- "Given a user's speech transcript, decide if it is clear enough to send to the agent. " +
64
- "If unclear or missing key details, ask exactly one short question. " +
65
- "If clear, rewrite it as a concise instruction for the agent. " +
66
- "Keep it short and preserve intent.",
67
- model: DEFAULT_CONTROLLER_MODEL,
68
- outputType: steeringSchema,
69
- });
70
-
71
- try {
72
- const result = await run(agent, normalized);
73
- return result.finalOutput ?? null;
74
- } catch (error) {
75
- logger.debug("voice-controller: steering error", {
76
- error: error instanceof Error ? error.message : String(error),
77
- });
78
- return null;
79
- }
80
- }
81
-
82
- async decidePresentation(text: string): Promise<VoicePresentationDecision | null> {
83
- if (!(await this.ensureApiKey())) return null;
84
-
85
- const normalized = truncateText(normalizeText(text), MAX_INPUT_CHARS);
86
- const agent = new Agent({
87
- name: "Voice Presentation Gate",
88
- instructions:
89
- "You are a voice presentation gate for a coding agent. " +
90
- "Decide whether to speak the assistant response to the user. " +
91
- "Speak when there is a decision, summary, or a question for the user. " +
92
- "Skip if it is mostly tool output, verbose logs, or not useful to speak. " +
93
- "When speaking, respond in 1-3 short sentences (<=45 words) in a casual, concise tone. " +
94
- "If user input is needed, ask exactly one short question.",
95
- model: DEFAULT_CONTROLLER_MODEL,
96
- outputType: presentationSchema,
97
- });
98
-
99
- try {
100
- const result = await run(agent, normalized);
101
- return result.finalOutput ?? null;
102
- } catch (error) {
103
- logger.debug("voice-controller: presentation error", {
104
- error: error instanceof Error ? error.message : String(error),
105
- });
106
- return null;
107
- }
108
- }
109
-
110
- async summarizeForVoice(text: string): Promise<string | null> {
111
- if (!(await this.ensureApiKey())) return null;
112
-
113
- const normalized = truncateText(normalizeText(text), MAX_INPUT_CHARS);
114
- const agent = new Agent({
115
- name: "Voice Summary",
116
- instructions:
117
- "Summarize the assistant response for voice playback. " +
118
- "Use 1-2 short sentences. " +
119
- "If a question is required from the user, ask one short question.",
120
- model: DEFAULT_SUMMARY_MODEL,
121
- outputType: summarySchema,
122
- });
123
-
124
- try {
125
- const result = await run(agent, normalized);
126
- const output = result.finalOutput?.text ?? "";
127
- return output.trim() || null;
128
- } catch (error) {
129
- logger.debug("voice-controller: summary error", {
130
- error: error instanceof Error ? error.message : String(error),
131
- });
132
- return null;
133
- }
134
- }
135
- }