@iinm/plain-agent 1.8.3 → 1.8.4

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.
Files changed (86) hide show
  1. package/README.md +2 -2
  2. package/bin/plain +1 -1
  3. package/config/config.predefined.json +1 -1
  4. package/config/prompts.predefined/shortcuts/configure.md +1 -1
  5. package/dist/main.mjs +473 -0
  6. package/dist/main.mjs.map +7 -0
  7. package/package.json +5 -7
  8. package/src/agent.d.ts +0 -52
  9. package/src/agent.mjs +0 -204
  10. package/src/agentLoop.mjs +0 -419
  11. package/src/agentState.mjs +0 -41
  12. package/src/claudeCodePlugin.mjs +0 -164
  13. package/src/cliArgs.mjs +0 -175
  14. package/src/cliBatch.mjs +0 -147
  15. package/src/cliCommands.mjs +0 -283
  16. package/src/cliCompleter.mjs +0 -227
  17. package/src/cliCost.mjs +0 -309
  18. package/src/cliFormatter.mjs +0 -413
  19. package/src/cliInteractive.mjs +0 -529
  20. package/src/cliInterruptTransform.mjs +0 -51
  21. package/src/cliMuteTransform.mjs +0 -26
  22. package/src/cliPasteTransform.mjs +0 -183
  23. package/src/config.d.ts +0 -36
  24. package/src/config.mjs +0 -197
  25. package/src/context/loadAgentRoles.mjs +0 -283
  26. package/src/context/loadPrompts.mjs +0 -324
  27. package/src/context/loadUserMessageContext.mjs +0 -147
  28. package/src/costTracker.mjs +0 -210
  29. package/src/env.mjs +0 -44
  30. package/src/main.mjs +0 -279
  31. package/src/mcpClient.mjs +0 -351
  32. package/src/mcpIntegration.mjs +0 -160
  33. package/src/model.d.ts +0 -109
  34. package/src/modelCaller.mjs +0 -32
  35. package/src/modelDefinition.d.ts +0 -92
  36. package/src/prompt.mjs +0 -138
  37. package/src/providers/anthropic.d.ts +0 -248
  38. package/src/providers/anthropic.mjs +0 -587
  39. package/src/providers/bedrock.d.ts +0 -249
  40. package/src/providers/bedrock.mjs +0 -700
  41. package/src/providers/gemini.d.ts +0 -208
  42. package/src/providers/gemini.mjs +0 -754
  43. package/src/providers/openai.d.ts +0 -281
  44. package/src/providers/openai.mjs +0 -544
  45. package/src/providers/openaiCompatible.d.ts +0 -147
  46. package/src/providers/openaiCompatible.mjs +0 -652
  47. package/src/providers/platform/awsSigV4.mjs +0 -184
  48. package/src/providers/platform/azure.mjs +0 -42
  49. package/src/providers/platform/bedrock.mjs +0 -78
  50. package/src/providers/platform/googleCloud.mjs +0 -34
  51. package/src/subagent.mjs +0 -265
  52. package/src/tmpfile.mjs +0 -27
  53. package/src/tool.d.ts +0 -74
  54. package/src/toolExecutor.mjs +0 -236
  55. package/src/toolInputValidator.mjs +0 -183
  56. package/src/toolUseApprover.mjs +0 -99
  57. package/src/tools/askURL.mjs +0 -209
  58. package/src/tools/askWeb.mjs +0 -208
  59. package/src/tools/compactContext.d.ts +0 -4
  60. package/src/tools/compactContext.mjs +0 -87
  61. package/src/tools/delegateToSubagent.d.ts +0 -4
  62. package/src/tools/delegateToSubagent.mjs +0 -48
  63. package/src/tools/execCommand.d.ts +0 -22
  64. package/src/tools/execCommand.mjs +0 -200
  65. package/src/tools/patchFile.d.ts +0 -4
  66. package/src/tools/patchFile.mjs +0 -133
  67. package/src/tools/reportAsSubagent.d.ts +0 -3
  68. package/src/tools/reportAsSubagent.mjs +0 -44
  69. package/src/tools/tmuxCommand.d.ts +0 -14
  70. package/src/tools/tmuxCommand.mjs +0 -194
  71. package/src/tools/writeFile.d.ts +0 -4
  72. package/src/tools/writeFile.mjs +0 -56
  73. package/src/usageStore.mjs +0 -167
  74. package/src/utils/evalJSONConfig.mjs +0 -72
  75. package/src/utils/matchValue.d.ts +0 -6
  76. package/src/utils/matchValue.mjs +0 -40
  77. package/src/utils/noThrow.mjs +0 -31
  78. package/src/utils/notify.mjs +0 -29
  79. package/src/utils/parseFileRange.mjs +0 -18
  80. package/src/utils/readFileRange.mjs +0 -33
  81. package/src/utils/retryOnError.mjs +0 -41
  82. package/src/voiceInput.mjs +0 -61
  83. package/src/voiceInputGemini.mjs +0 -105
  84. package/src/voiceInputOpenAI.mjs +0 -104
  85. package/src/voiceInputSession.mjs +0 -543
  86. package/src/voiceToggleKey.mjs +0 -62
@@ -1,209 +0,0 @@
1
- /**
2
- * @import { Tool } from '../tool'
3
- */
4
-
5
- import { styleText } from "node:util";
6
- import { getGoogleCloudAccessToken } from "../providers/platform/googleCloud.mjs";
7
- import { noThrow } from "../utils/noThrow.mjs";
8
-
9
- /** @typedef {AskURLToolGeminiOptions | AskURLToolGeminiVertexAIOptions} AskURLToolOptions */
10
-
11
- /**
12
- * @typedef {Object} AskURLToolGeminiOptions
13
- * @property {"gemini"} provider
14
- * @property {string=} baseURL
15
- * @property {string} apiKey
16
- * @property {string} model
17
- */
18
-
19
- /**
20
- * @typedef {Object} AskURLToolGeminiVertexAIOptions
21
- * @property {"gemini-vertex-ai"} provider
22
- * @property {string} baseURL
23
- * @property {string=} account
24
- * @property {string} model
25
- */
26
-
27
- /**
28
- * @typedef {Object} AskURLInput
29
- * @property {string} question
30
- */
31
-
32
- /**
33
- * @param {AskURLToolOptions} config
34
- * @returns {Tool}
35
- */
36
- export function createAskURLTool(config) {
37
- /**
38
- * @param {AskURLInput} input
39
- * @param {number} retryCount
40
- * @returns {Promise<string | Error>}
41
- */
42
- async function askURL(input, retryCount = 0) {
43
- const model = config.model ?? "gemini-3-flash-preview";
44
- const url =
45
- config.provider === "gemini-vertex-ai"
46
- ? `${config.baseURL}/publishers/google/models/${config.model}:generateContent`
47
- : config.baseURL
48
- ? `${config.baseURL}/models/${model}:generateContent`
49
- : `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent`;
50
-
51
- /** @type {Record<string,string>} */
52
- const authHeader =
53
- config.provider === "gemini-vertex-ai"
54
- ? {
55
- Authorization: `Bearer ${await getGoogleCloudAccessToken(config.account)}`,
56
- }
57
- : {
58
- "x-goog-api-key": config.apiKey ?? "",
59
- };
60
-
61
- const data = {
62
- contents: [
63
- {
64
- role: "user",
65
- parts: [
66
- {
67
- text: `I need a comprehensive answer to this question. Please note that I don't have access to external URLs, so include all relevant facts, data, or explanations directly in your response. Avoid referencing links I can't open.
68
-
69
- Question: ${input.question}`,
70
- },
71
- ],
72
- },
73
- ],
74
- tools: [
75
- {
76
- url_context: {},
77
- },
78
- ],
79
- };
80
-
81
- const response = await fetch(url, {
82
- method: "POST",
83
- headers: {
84
- ...authHeader,
85
- "Content-Type": "application/json",
86
- },
87
- body: JSON.stringify(data),
88
- signal: AbortSignal.timeout(120 * 1000),
89
- });
90
-
91
- if (response.status === 429 || response.status >= 500) {
92
- const interval = Math.min(2 * 2 ** retryCount, 16);
93
- console.error(
94
- styleText(
95
- "yellow",
96
- `Google API returned ${response.status}. Retrying in ${interval} seconds...`,
97
- ),
98
- );
99
- await new Promise((resolve) => setTimeout(resolve, interval * 1000));
100
- return askURL(input, retryCount + 1);
101
- }
102
-
103
- if (!response.ok) {
104
- return new Error(
105
- `Failed to ask Web: status=${response.status}, body=${await response.text()}`,
106
- );
107
- }
108
-
109
- const body = await response.json();
110
-
111
- const candidate = body.candidates?.[0];
112
- const text = candidate?.content?.parts?.[0]?.text;
113
- /** @type {{segment?:{startIndex:number,endIndex:number,text:string},groundingChunkIndices?:number[]}[] | undefined} */
114
- const supports = candidate?.groundingMetadata?.groundingSupports;
115
- /** @type {{web?:{uri:string,title:string}}[] | undefined} */
116
- const chunks = candidate?.groundingMetadata?.groundingChunks;
117
-
118
- if (typeof text !== "string") {
119
- return new Error(
120
- `Unexpected response format from Google: ${JSON.stringify(body)}`,
121
- );
122
- }
123
-
124
- /**
125
- * @param {string} source
126
- * @param {number} byteIndex
127
- * @param {string} insertText
128
- */
129
- const insertTextAtUtf8ByteIndex = (source, byteIndex, insertText) => {
130
- const sourceBuffer = Buffer.from(source, "utf8");
131
- const normalizedByteIndex = Math.max(
132
- 0,
133
- Math.min(byteIndex, sourceBuffer.length),
134
- );
135
-
136
- return Buffer.concat([
137
- sourceBuffer.subarray(0, normalizedByteIndex),
138
- Buffer.from(insertText, "utf8"),
139
- sourceBuffer.subarray(normalizedByteIndex),
140
- ]).toString("utf8");
141
- };
142
-
143
- // Sort by end_index desc because Gemini grounding indexes are byte offsets
144
- // into the original UTF-8 text.
145
- const sortedSupports = supports?.toSorted(
146
- (a, b) => (b.segment?.endIndex ?? 0) - (a.segment?.endIndex ?? 0),
147
- );
148
-
149
- // Insert citations using UTF-8 byte offsets.
150
- let textWithCitations = text;
151
- for (const support of sortedSupports ?? []) {
152
- const endIndex = support.segment?.endIndex;
153
- if (
154
- typeof endIndex !== "number" ||
155
- !support.groundingChunkIndices?.length
156
- ) {
157
- continue;
158
- }
159
-
160
- textWithCitations = insertTextAtUtf8ByteIndex(
161
- textWithCitations,
162
- endIndex,
163
- ` [${support.groundingChunkIndices.map((i) => i + 1).join(", ")}] `,
164
- );
165
- }
166
-
167
- const chunkString = (chunks ?? [])
168
- .map(
169
- (chunk, index) =>
170
- `- [${index + 1} - ${chunk.web?.title}](${chunk.web?.uri})`,
171
- )
172
- .join("\n");
173
-
174
- return [textWithCitations, chunkString].join("\n\n");
175
- }
176
-
177
- return {
178
- def: {
179
- name: "ask_url",
180
- description:
181
- "Use one or more provided URLs to answer a question. Include the URLs in your question.",
182
- inputSchema: {
183
- type: "object",
184
- properties: {
185
- question: {
186
- type: "string",
187
- description:
188
- "The question to ask, including one or more URLs to use as context.",
189
- },
190
- },
191
- required: ["question"],
192
- },
193
- },
194
-
195
- /**
196
- * @param {AskURLInput} input
197
- * @returns {Promise<string | Error>}
198
- */
199
- impl: async (input) => await noThrow(async () => askURL(input, 0)),
200
-
201
- /**
202
- * @param {Record<string, unknown>} _input
203
- * @returns {Record<string, unknown>}
204
- */
205
- maskApprovalInput: (_input) => {
206
- return {};
207
- },
208
- };
209
- }
@@ -1,208 +0,0 @@
1
- /**
2
- * @import { Tool } from '../tool'
3
- */
4
-
5
- import { styleText } from "node:util";
6
- import { getGoogleCloudAccessToken } from "../providers/platform/googleCloud.mjs";
7
- import { noThrow } from "../utils/noThrow.mjs";
8
-
9
- /** @typedef {AskWebToolGeminiOptions | AskWebToolGeminiVertexAIOptions} AskWebToolOptions */
10
-
11
- /**
12
- * @typedef {Object} AskWebToolGeminiOptions
13
- * @property {"gemini"} provider
14
- * @property {string=} baseURL
15
- * @property {string} apiKey
16
- * @property {string} model
17
- */
18
-
19
- /**
20
- * @typedef {Object} AskWebToolGeminiVertexAIOptions
21
- * @property {"gemini-vertex-ai"} provider
22
- * @property {string} baseURL
23
- * @property {string=} account
24
- * @property {string} model
25
- */
26
-
27
- /**
28
- * @typedef {Object} AskWebInput
29
- * @property {string} question
30
- */
31
-
32
- /**
33
- * @param {AskWebToolOptions} config
34
- * @returns {Tool}
35
- */
36
- export function createAskWebTool(config) {
37
- /**
38
- * @param {AskWebInput} input
39
- * @param {number} retryCount
40
- * @returns {Promise<string | Error>}
41
- */
42
- async function askWeb(input, retryCount = 0) {
43
- const model = config.model ?? "gemini-3-flash-preview";
44
- const url =
45
- config.provider === "gemini-vertex-ai"
46
- ? `${config.baseURL}/publishers/google/models/${config.model}:generateContent`
47
- : config.baseURL
48
- ? `${config.baseURL}/models/${model}:generateContent`
49
- : `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent`;
50
-
51
- /** @type {Record<string,string>} */
52
- const authHeader =
53
- config.provider === "gemini-vertex-ai"
54
- ? {
55
- Authorization: `Bearer ${await getGoogleCloudAccessToken(config.account)}`,
56
- }
57
- : {
58
- "x-goog-api-key": config.apiKey ?? "",
59
- };
60
-
61
- const data = {
62
- contents: [
63
- {
64
- role: "user",
65
- parts: [
66
- {
67
- text: `I need a comprehensive answer to this question. Please note that I don't have access to external URLs, so include all relevant facts, data, or explanations directly in your response. Avoid referencing links I can't open.
68
-
69
- Question: ${input.question}`,
70
- },
71
- ],
72
- },
73
- ],
74
- tools: [
75
- {
76
- google_search: {},
77
- },
78
- ],
79
- };
80
-
81
- const response = await fetch(url, {
82
- method: "POST",
83
- headers: {
84
- ...authHeader,
85
- "Content-Type": "application/json",
86
- },
87
- body: JSON.stringify(data),
88
- signal: AbortSignal.timeout(120 * 1000),
89
- });
90
-
91
- if (response.status === 429 || response.status >= 500) {
92
- const interval = Math.min(2 * 2 ** retryCount, 16);
93
- console.error(
94
- styleText(
95
- "yellow",
96
- `Google API returned ${response.status}. Retrying in ${interval} seconds...`,
97
- ),
98
- );
99
- await new Promise((resolve) => setTimeout(resolve, interval * 1000));
100
- return askWeb(input, retryCount + 1);
101
- }
102
-
103
- if (!response.ok) {
104
- return new Error(
105
- `Failed to ask Web: status=${response.status}, body=${await response.text()}`,
106
- );
107
- }
108
-
109
- const body = await response.json();
110
-
111
- const candidate = body.candidates?.[0];
112
- const text = candidate?.content?.parts?.[0]?.text;
113
- /** @type {{segment?:{startIndex:number,endIndex:number,text:string},groundingChunkIndices?:number[]}[] | undefined} */
114
- const supports = candidate?.groundingMetadata?.groundingSupports;
115
- /** @type {{web?:{uri:string,title:string}}[] | undefined} */
116
- const chunks = candidate?.groundingMetadata?.groundingChunks;
117
-
118
- if (typeof text !== "string") {
119
- return new Error(
120
- `Unexpected response format from Google: ${JSON.stringify(body)}`,
121
- );
122
- }
123
-
124
- /**
125
- * @param {string} source
126
- * @param {number} byteIndex
127
- * @param {string} insertText
128
- */
129
- const insertTextAtUtf8ByteIndex = (source, byteIndex, insertText) => {
130
- const sourceBuffer = Buffer.from(source, "utf8");
131
- const normalizedByteIndex = Math.max(
132
- 0,
133
- Math.min(byteIndex, sourceBuffer.length),
134
- );
135
-
136
- return Buffer.concat([
137
- sourceBuffer.subarray(0, normalizedByteIndex),
138
- Buffer.from(insertText, "utf8"),
139
- sourceBuffer.subarray(normalizedByteIndex),
140
- ]).toString("utf8");
141
- };
142
-
143
- // Sort by end_index desc because Gemini grounding indexes are byte offsets
144
- // into the original UTF-8 text.
145
- const sortedSupports = supports?.toSorted(
146
- (a, b) => (b.segment?.endIndex ?? 0) - (a.segment?.endIndex ?? 0),
147
- );
148
-
149
- // Insert citations using UTF-8 byte offsets.
150
- let textWithCitations = text;
151
- for (const support of sortedSupports ?? []) {
152
- const endIndex = support.segment?.endIndex;
153
- if (
154
- typeof endIndex !== "number" ||
155
- !support.groundingChunkIndices?.length
156
- ) {
157
- continue;
158
- }
159
-
160
- textWithCitations = insertTextAtUtf8ByteIndex(
161
- textWithCitations,
162
- endIndex,
163
- ` [${support.groundingChunkIndices.map((i) => i + 1).join(", ")}] `,
164
- );
165
- }
166
-
167
- const chunkString = (chunks ?? [])
168
- .map(
169
- (chunk, index) =>
170
- `- [${index + 1} - ${chunk.web?.title}](${chunk.web?.uri})`,
171
- )
172
- .join("\n");
173
-
174
- return [textWithCitations, chunkString].join("\n\n");
175
- }
176
-
177
- return {
178
- def: {
179
- name: "ask_web",
180
- description:
181
- "Use the web search to answer questions that need up-to-date information or supporting sources.",
182
- inputSchema: {
183
- type: "object",
184
- properties: {
185
- question: {
186
- type: "string",
187
- description: "The question to ask",
188
- },
189
- },
190
- required: ["question"],
191
- },
192
- },
193
-
194
- /**
195
- * @param {AskWebInput} input
196
- * @returns {Promise<string | Error>}
197
- */
198
- impl: async (input) => await noThrow(async () => askWeb(input, 0)),
199
-
200
- /**
201
- * @param {Record<string, unknown>} _input
202
- * @returns {Record<string, unknown>}
203
- */
204
- maskApprovalInput: (_input) => {
205
- return {};
206
- },
207
- };
208
- }
@@ -1,4 +0,0 @@
1
- export type CompactContextInput = {
2
- memoryPath: string;
3
- reason: string;
4
- };
@@ -1,87 +0,0 @@
1
- /**
2
- * @import { Tool, ToolImplementation } from '../tool'
3
- * @import { CompactContextInput } from './compactContext'
4
- */
5
-
6
- import fs from "node:fs/promises";
7
- import path from "node:path";
8
- import { AGENT_MEMORY_DIR } from "../env.mjs";
9
- import { noThrow } from "../utils/noThrow.mjs";
10
-
11
- export const compactContextToolName = "compact_context";
12
-
13
- /** @returns {Tool} */
14
- export function createCompactContextTool() {
15
- /** @type {ToolImplementation} */
16
- let impl = async () => {
17
- throw new Error("Not implemented");
18
- };
19
-
20
- /** @type {Tool} */
21
- const tool = {
22
- def: {
23
- name: compactContextToolName,
24
- description:
25
- "Discard prior messages and reload task state from a memory file.",
26
- inputSchema: {
27
- type: "object",
28
- properties: {
29
- memoryPath: {
30
- type: "string",
31
- description: `Path to the memory file under ${AGENT_MEMORY_DIR}/.`,
32
- },
33
- reason: {
34
- type: "string",
35
- description: "The reason for compacting the context.",
36
- },
37
- },
38
- required: ["memoryPath", "reason"],
39
- },
40
- },
41
-
42
- // Implementation is injected by the agent so it can access subagent
43
- // state (compact_context is not allowed during subagent execution).
44
- get impl() {
45
- return impl;
46
- },
47
-
48
- injectImpl(fn) {
49
- impl = fn;
50
- },
51
- };
52
-
53
- return tool;
54
- }
55
-
56
- /**
57
- * Read a memory file and return the compact_context tool result string.
58
- * Validates that the memoryPath is within the project memory directory.
59
- * @param {CompactContextInput} input
60
- * @returns {Promise<string | Error>}
61
- */
62
- export async function readMemoryForCompaction(input) {
63
- return await noThrow(async () => {
64
- const absolutePath = path.resolve(input.memoryPath);
65
- const memoryDir = path.resolve(AGENT_MEMORY_DIR);
66
- const relativePath = path.relative(memoryDir, absolutePath);
67
- if (relativePath.startsWith("..") || path.isAbsolute(relativePath)) {
68
- return new Error(
69
- `Access denied: memoryPath must be within ${AGENT_MEMORY_DIR}`,
70
- );
71
- }
72
-
73
- const memoryContent = await fs.readFile(absolutePath, {
74
- encoding: "utf-8",
75
- });
76
-
77
- return [
78
- "Context compacted. Prior conversation has been discarded.",
79
- `Reason: ${input.reason}`,
80
- `Memory file: ${input.memoryPath}`,
81
- "",
82
- "Resume the task using the memory file contents below.",
83
- "",
84
- memoryContent,
85
- ].join("\n");
86
- });
87
- }
@@ -1,4 +0,0 @@
1
- export type DelegateToSubagentInput = {
2
- name: string;
3
- goal: string;
4
- };
@@ -1,48 +0,0 @@
1
- /**
2
- * @import { Tool, ToolImplementation } from '../tool'
3
- */
4
-
5
- export const delegateToSubagentToolName = "delegate_to_subagent";
6
-
7
- /** @returns {Tool} */
8
- export function createDelegateToSubagentTool() {
9
- /** @type {ToolImplementation} */
10
- let impl = async () => {
11
- throw new Error("Not implemented");
12
- };
13
-
14
- /** @type {Tool} */
15
- const tool = {
16
- def: {
17
- name: delegateToSubagentToolName,
18
- description:
19
- "Delegate a subtask to a subagent. You inherit the current context and work on the delegated goal.",
20
- inputSchema: {
21
- type: "object",
22
- properties: {
23
- name: {
24
- type: "string",
25
- description:
26
- "Role or name of the subagent. Use 'custom:' prefix for ad-hoc roles.",
27
- },
28
- goal: {
29
- type: "string",
30
- description: "The goal or task for the subagent to achieve.",
31
- },
32
- },
33
- required: ["name", "goal"],
34
- },
35
- },
36
-
37
- // Implementation will be injected by the agent to access its state
38
- get impl() {
39
- return impl;
40
- },
41
-
42
- injectImpl(fn) {
43
- impl = fn;
44
- },
45
- };
46
-
47
- return tool;
48
- }
@@ -1,22 +0,0 @@
1
- export type ExecCommandInput = {
2
- command: string;
3
- args?: string[];
4
- };
5
-
6
- export type ExecCommandConfig = {
7
- sandbox?: ExecCommandSanboxConfig;
8
- };
9
-
10
- export type ExecCommandSanboxConfig = {
11
- command: string;
12
- args?: string[];
13
- separator?: string;
14
- rules?: {
15
- pattern: {
16
- command: string;
17
- args?: string[];
18
- };
19
- mode: "sandbox" | "unsandboxed";
20
- additionalArgs?: string[];
21
- }[];
22
- };