@markusylisiurunen/tau 0.2.62 → 0.2.64
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/README.md +1 -1
- package/dist/core/async/cli.js +68 -104
- package/dist/core/async/cli.js.map +1 -1
- package/dist/core/async/http_protocol.js +119 -8
- package/dist/core/async/http_protocol.js.map +1 -1
- package/dist/core/async/http_server.js +120 -240
- package/dist/core/async/http_server.js.map +1 -1
- package/dist/core/async/index.js +1 -1
- package/dist/core/async/index.js.map +1 -1
- package/dist/core/async/server_config.js +163 -341
- package/dist/core/async/server_config.js.map +1 -1
- package/dist/core/async/telegram.js +191 -133
- package/dist/core/async/telegram.js.map +1 -1
- package/dist/core/cli.js +25 -31
- package/dist/core/cli.js.map +1 -1
- package/dist/core/config/content_loader.js +57 -205
- package/dist/core/config/content_loader.js.map +1 -1
- package/dist/core/config/markdown_frontmatter.js +34 -0
- package/dist/core/config/markdown_frontmatter.js.map +1 -0
- package/dist/core/config/runtime.js +6 -1
- package/dist/core/config/runtime.js.map +1 -1
- package/dist/core/config/schema.js +268 -327
- package/dist/core/config/schema.js.map +1 -1
- package/dist/core/config/skill_parser.js +8 -32
- package/dist/core/config/skill_parser.js.map +1 -1
- package/dist/core/config/skills_loader.js +32 -18
- package/dist/core/config/skills_loader.js.map +1 -1
- package/dist/core/events/index.js +1 -0
- package/dist/core/events/index.js.map +1 -1
- package/dist/core/events/parser.js +115 -0
- package/dist/core/events/parser.js.map +1 -0
- package/dist/core/index.js +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/modes/rpc_protocol.js +249 -189
- package/dist/core/modes/rpc_protocol.js.map +1 -1
- package/dist/core/persona_reference.js +27 -0
- package/dist/core/persona_reference.js.map +1 -0
- package/dist/core/tools/send_input_to_agent.js +7 -12
- package/dist/core/tools/send_input_to_agent.js.map +1 -1
- package/dist/core/tools/spawn_agent.js +13 -14
- package/dist/core/tools/spawn_agent.js.map +1 -1
- package/dist/core/tools/terminate_agent.js +6 -11
- package/dist/core/tools/terminate_agent.js.map +1 -1
- package/dist/core/tools/wait_for_agent.js +6 -11
- package/dist/core/tools/wait_for_agent.js.map +1 -1
- package/dist/core/utils/mistral_transcription.js +13 -21
- package/dist/core/utils/mistral_transcription.js.map +1 -1
- package/dist/core/utils/parallel_api.js +15 -19
- package/dist/core/utils/parallel_api.js.map +1 -1
- package/dist/core/utils/zod.js +7 -0
- package/dist/core/utils/zod.js.map +1 -1
- package/dist/core/version.js +1 -1
- package/dist/main.js +21 -3
- package/dist/main.js.map +1 -1
- package/dist/tui/app.js.map +1 -1
- package/dist/tui/chat_controller/session_maintenance_service.js +98 -171
- package/dist/tui/chat_controller/session_maintenance_service.js.map +1 -1
- package/dist/tui/chat_controller.js +48 -10
- package/dist/tui/chat_controller.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,13 +1,78 @@
|
|
|
1
1
|
import { resolve } from "node:path";
|
|
2
2
|
import { getModels, getProviders } from "@mariozechner/pi-ai";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { formatPersonaReference, parsePersonaReference } from "../persona_reference.js";
|
|
3
5
|
import { parseSubagentLaunchModelList } from "../subagents/launch_model.js";
|
|
4
|
-
import { RiskLevelSchema } from "../types.js";
|
|
6
|
+
import { REASONING_LEVELS, RiskLevelSchema } from "../types.js";
|
|
5
7
|
import { normalizeModelNoticeKey, parseModelNoticeKey } from "../utils/model_notices.js";
|
|
6
|
-
import { isRecord } from "../utils/type_guards.js";
|
|
7
8
|
import { parseBashCommands } from "./bash_commands.js";
|
|
8
9
|
import { createDefaultConfigDeps } from "./deps.js";
|
|
9
10
|
import { resolveConfigLevels } from "./paths.js";
|
|
10
11
|
import { getVirtualConfigDefaults } from "./virtual_defaults.js";
|
|
12
|
+
const NonEmptyStringSchema = z.string().trim().min(1);
|
|
13
|
+
const BooleanSchema = z.boolean();
|
|
14
|
+
const AgentContextFilesSchema = z.array(NonEmptyStringSchema);
|
|
15
|
+
const ApiKeyProviderSchema = z.string();
|
|
16
|
+
const ApiKeysSchema = z
|
|
17
|
+
.object({
|
|
18
|
+
anthropic: z.unknown().optional(),
|
|
19
|
+
google: z.unknown().optional(),
|
|
20
|
+
openai: z.unknown().optional(),
|
|
21
|
+
parallel: z.unknown().optional(),
|
|
22
|
+
mistral: z.unknown().optional(),
|
|
23
|
+
})
|
|
24
|
+
.passthrough();
|
|
25
|
+
const SandboxSchema = z
|
|
26
|
+
.object({
|
|
27
|
+
image: z.unknown().optional(),
|
|
28
|
+
mountPath: z.unknown().optional(),
|
|
29
|
+
pruneAfterHours: z.unknown().optional(),
|
|
30
|
+
extraDockerArgs: z.unknown().optional(),
|
|
31
|
+
environmentInfo: z.unknown().optional(),
|
|
32
|
+
})
|
|
33
|
+
.passthrough();
|
|
34
|
+
const SandboxFieldsSchema = z.object({
|
|
35
|
+
image: NonEmptyStringSchema,
|
|
36
|
+
mountPath: NonEmptyStringSchema,
|
|
37
|
+
pruneAfterHours: z.number().finite().gt(0),
|
|
38
|
+
extraDockerArgs: z.array(z.string().refine((entry) => entry.trim().length > 0)),
|
|
39
|
+
environmentInfo: z.string(),
|
|
40
|
+
});
|
|
41
|
+
const SubagentsConfigSchema = z
|
|
42
|
+
.object({
|
|
43
|
+
defaultLaunchModels: z.array(z.string()).optional(),
|
|
44
|
+
})
|
|
45
|
+
.passthrough();
|
|
46
|
+
const StringRecordSchema = z.object({}).catchall(z.unknown());
|
|
47
|
+
const PositiveIntegerSchema = z.number().int().finite().gt(0);
|
|
48
|
+
const AsyncClientTargetSchema = z.object({
|
|
49
|
+
url: NonEmptyStringSchema,
|
|
50
|
+
token: NonEmptyStringSchema,
|
|
51
|
+
timeoutMs: PositiveIntegerSchema.optional(),
|
|
52
|
+
});
|
|
53
|
+
function parseOptionalFields(data, sourceLabel, specs) {
|
|
54
|
+
const values = {};
|
|
55
|
+
const errors = [];
|
|
56
|
+
for (const [key, schema, errorMessage] of specs) {
|
|
57
|
+
const value = data[key];
|
|
58
|
+
if (value === undefined) {
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
const parsed = schema.safeParse(value);
|
|
62
|
+
if (!parsed.success) {
|
|
63
|
+
errors.push(`${sourceLabel}: ${errorMessage}`);
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
values[key] = parsed.data;
|
|
67
|
+
}
|
|
68
|
+
return { values, errors };
|
|
69
|
+
}
|
|
70
|
+
function assignParsedConfigValue(config, errors, key, value, parseErrors) {
|
|
71
|
+
if (value !== undefined) {
|
|
72
|
+
config[key] = value;
|
|
73
|
+
}
|
|
74
|
+
errors.push(...parseErrors);
|
|
75
|
+
}
|
|
11
76
|
function parseConfigJson(content, sourceLabel) {
|
|
12
77
|
try {
|
|
13
78
|
return { data: JSON.parse(content), errors: [] };
|
|
@@ -20,244 +85,179 @@ function parseConfigJson(content, sourceLabel) {
|
|
|
20
85
|
};
|
|
21
86
|
}
|
|
22
87
|
}
|
|
23
|
-
function
|
|
24
|
-
if (
|
|
25
|
-
return {
|
|
88
|
+
function parseApiKeysConfig(raw, sourceLabel) {
|
|
89
|
+
if (raw === undefined) {
|
|
90
|
+
return { errors: [] };
|
|
26
91
|
}
|
|
27
|
-
const
|
|
28
|
-
|
|
92
|
+
const parsed = ApiKeysSchema.safeParse(raw);
|
|
93
|
+
if (!parsed.success) {
|
|
94
|
+
return { errors: [`${sourceLabel}: 'apiKeys' must be an object.`] };
|
|
95
|
+
}
|
|
96
|
+
const apiKeys = {};
|
|
29
97
|
const errors = [];
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
98
|
+
for (const provider of ["anthropic", "google", "openai", "parallel", "mistral"]) {
|
|
99
|
+
const value = parsed.data[provider];
|
|
100
|
+
if (value === undefined) {
|
|
101
|
+
continue;
|
|
33
102
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
for (const provider of providers) {
|
|
39
|
-
const value = keys[provider];
|
|
40
|
-
if (value === undefined)
|
|
41
|
-
continue;
|
|
42
|
-
if (typeof value !== "string") {
|
|
43
|
-
errors.push(`${sourceLabel}: apiKeys.${provider} must be a string.`);
|
|
44
|
-
continue;
|
|
45
|
-
}
|
|
46
|
-
apiKeys[provider] = value;
|
|
47
|
-
}
|
|
48
|
-
if (Object.keys(apiKeys).length > 0) {
|
|
49
|
-
config.apiKeys = apiKeys;
|
|
50
|
-
}
|
|
103
|
+
const parsedValue = ApiKeyProviderSchema.safeParse(value);
|
|
104
|
+
if (!parsedValue.success) {
|
|
105
|
+
errors.push(`${sourceLabel}: apiKeys.${provider} must be a string.`);
|
|
106
|
+
continue;
|
|
51
107
|
}
|
|
108
|
+
apiKeys[provider] = parsedValue.data;
|
|
52
109
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
config.sandbox = sandboxResult.config;
|
|
56
|
-
}
|
|
57
|
-
errors.push(...sandboxResult.errors);
|
|
58
|
-
if (data.defaultPersona !== undefined) {
|
|
59
|
-
if (typeof data.defaultPersona === "string") {
|
|
60
|
-
const defaultPersona = data.defaultPersona.trim();
|
|
61
|
-
if (!defaultPersona) {
|
|
62
|
-
errors.push(`${sourceLabel}: 'defaultPersona' must be a non-empty string.`);
|
|
63
|
-
}
|
|
64
|
-
else if (defaultPersona.includes(":")) {
|
|
65
|
-
errors.push(`${sourceLabel}: 'defaultPersona' must be a persona id only (reasoning overrides are not supported).`);
|
|
66
|
-
}
|
|
67
|
-
else {
|
|
68
|
-
config.defaultPersona = defaultPersona;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
else {
|
|
72
|
-
errors.push(`${sourceLabel}: 'defaultPersona' must be a string.`);
|
|
73
|
-
}
|
|
110
|
+
if (Object.keys(apiKeys).length === 0) {
|
|
111
|
+
return { errors };
|
|
74
112
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
80
|
-
else {
|
|
81
|
-
errors.push(`${sourceLabel}: 'defaultRisk' must be a valid risk level.`);
|
|
82
|
-
}
|
|
113
|
+
return { config: apiKeys, errors };
|
|
114
|
+
}
|
|
115
|
+
function parseDefaultPersona(raw, sourceLabel) {
|
|
116
|
+
if (raw === undefined) {
|
|
117
|
+
return { errors: [] };
|
|
83
118
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}
|
|
88
|
-
else {
|
|
89
|
-
errors.push(`${sourceLabel}: 'disableBuiltinPersonas' must be a boolean.`);
|
|
90
|
-
}
|
|
119
|
+
const parsedPersona = z.string().safeParse(raw);
|
|
120
|
+
if (!parsedPersona.success) {
|
|
121
|
+
return { errors: [`${sourceLabel}: 'defaultPersona' must be a string.`] };
|
|
91
122
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
123
|
+
const ref = parsePersonaReference(parsedPersona.data);
|
|
124
|
+
if (ref.personaId && !ref.error) {
|
|
125
|
+
return {
|
|
126
|
+
defaultPersona: formatPersonaReference({
|
|
127
|
+
personaId: ref.personaId,
|
|
128
|
+
reasoning: ref.reasoning,
|
|
129
|
+
}),
|
|
130
|
+
errors: [],
|
|
131
|
+
};
|
|
99
132
|
}
|
|
100
|
-
if (
|
|
101
|
-
|
|
102
|
-
const defaultTheme = data.defaultTheme.trim();
|
|
103
|
-
if (!defaultTheme) {
|
|
104
|
-
errors.push(`${sourceLabel}: 'defaultTheme' must be a non-empty string.`);
|
|
105
|
-
}
|
|
106
|
-
else {
|
|
107
|
-
config.defaultTheme = defaultTheme;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
else {
|
|
111
|
-
errors.push(`${sourceLabel}: 'defaultTheme' must be a string.`);
|
|
112
|
-
}
|
|
133
|
+
if (ref.error === "empty-persona") {
|
|
134
|
+
return { errors: [`${sourceLabel}: 'defaultPersona' must be a non-empty string.`] };
|
|
113
135
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
136
|
+
if (ref.error === "missing-reasoning") {
|
|
137
|
+
return {
|
|
138
|
+
errors: [`${sourceLabel}: 'defaultPersona' is missing a reasoning level after ':'.`],
|
|
139
|
+
};
|
|
117
140
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
141
|
+
if (ref.error === "invalid-reasoning") {
|
|
142
|
+
return {
|
|
143
|
+
errors: [
|
|
144
|
+
`${sourceLabel}: 'defaultPersona' has invalid reasoning level '${ref.rawReasoning}'. allowed levels: ${REASONING_LEVELS.join(", ")}.`,
|
|
145
|
+
],
|
|
146
|
+
};
|
|
122
147
|
}
|
|
123
|
-
errors
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
148
|
+
return { errors: [] };
|
|
149
|
+
}
|
|
150
|
+
function validateConfigData(raw, sourceLabel) {
|
|
151
|
+
if (typeof raw !== "object" || raw === null) {
|
|
152
|
+
return { config: {}, errors: [`${sourceLabel}: config must be an object.`] };
|
|
127
153
|
}
|
|
128
|
-
|
|
154
|
+
const data = raw;
|
|
155
|
+
const config = {};
|
|
156
|
+
const errors = [];
|
|
157
|
+
const apiKeysResult = parseApiKeysConfig(data.apiKeys, sourceLabel);
|
|
158
|
+
assignParsedConfigValue(config, errors, "apiKeys", apiKeysResult.config, apiKeysResult.errors);
|
|
159
|
+
const sandboxResult = parseSandboxConfig(data.sandbox, sourceLabel);
|
|
160
|
+
assignParsedConfigValue(config, errors, "sandbox", sandboxResult.config, sandboxResult.errors);
|
|
161
|
+
const defaultPersonaResult = parseDefaultPersona(data.defaultPersona, sourceLabel);
|
|
162
|
+
assignParsedConfigValue(config, errors, "defaultPersona", defaultPersonaResult.defaultPersona, defaultPersonaResult.errors);
|
|
163
|
+
const scalarResult = parseOptionalFields(data, sourceLabel, [
|
|
164
|
+
["defaultRisk", RiskLevelSchema, "'defaultRisk' must be a valid risk level."],
|
|
165
|
+
["disableBuiltinPersonas", BooleanSchema, "'disableBuiltinPersonas' must be a boolean."],
|
|
166
|
+
["disableBuiltinThemes", BooleanSchema, "'disableBuiltinThemes' must be a boolean."],
|
|
167
|
+
["defaultTheme", NonEmptyStringSchema, "'defaultTheme' must be a non-empty string."],
|
|
168
|
+
]);
|
|
169
|
+
Object.assign(config, scalarResult.values);
|
|
170
|
+
errors.push(...scalarResult.errors);
|
|
171
|
+
const bashResult = parseBashCommands(data.bashCommands, sourceLabel);
|
|
172
|
+
assignParsedConfigValue(config, errors, "bashCommands", bashResult.commands.length > 0 ? bashResult.commands : undefined, bashResult.errors);
|
|
173
|
+
const agentResult = parseAgentContextFiles(data.agentContextFiles, sourceLabel);
|
|
174
|
+
assignParsedConfigValue(config, errors, "agentContextFiles", agentResult.paths.length > 0 ? agentResult.paths : undefined, agentResult.errors);
|
|
175
|
+
const subagentsResult = parseSubagentsConfig(data.subagents, sourceLabel);
|
|
176
|
+
assignParsedConfigValue(config, errors, "subagents", subagentsResult.config, subagentsResult.errors);
|
|
129
177
|
const modelSystemNoticesResult = parseModelSystemNotices(data.modelSystemNotices, sourceLabel);
|
|
130
|
-
|
|
131
|
-
config.modelSystemNotices = modelSystemNoticesResult.notices;
|
|
132
|
-
}
|
|
133
|
-
errors.push(...modelSystemNoticesResult.errors);
|
|
178
|
+
assignParsedConfigValue(config, errors, "modelSystemNotices", modelSystemNoticesResult.notices, modelSystemNoticesResult.errors);
|
|
134
179
|
const asyncResult = parseAsyncConfig(data.async, sourceLabel);
|
|
135
|
-
|
|
136
|
-
config.async = asyncResult.config;
|
|
137
|
-
}
|
|
138
|
-
errors.push(...asyncResult.errors);
|
|
180
|
+
assignParsedConfigValue(config, errors, "async", asyncResult.config, asyncResult.errors);
|
|
139
181
|
return { config, errors };
|
|
140
182
|
}
|
|
141
183
|
function parseSandboxConfig(raw, sourceLabel) {
|
|
142
184
|
if (raw === undefined) {
|
|
143
185
|
return { errors: [] };
|
|
144
186
|
}
|
|
145
|
-
|
|
187
|
+
const parsed = SandboxSchema.safeParse(raw);
|
|
188
|
+
if (!parsed.success) {
|
|
146
189
|
return { errors: [`${sourceLabel}: 'sandbox' must be an object.`] };
|
|
147
190
|
}
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
sandbox.
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
if (data.extraDockerArgs !== undefined) {
|
|
178
|
-
if (Array.isArray(data.extraDockerArgs)) {
|
|
179
|
-
const args = [];
|
|
180
|
-
let invalid = false;
|
|
181
|
-
for (const entry of data.extraDockerArgs) {
|
|
182
|
-
if (typeof entry !== "string" || !entry.trim()) {
|
|
183
|
-
invalid = true;
|
|
184
|
-
continue;
|
|
185
|
-
}
|
|
186
|
-
args.push(entry);
|
|
187
|
-
}
|
|
188
|
-
if (invalid) {
|
|
189
|
-
errors.push(`${sourceLabel}: sandbox.extraDockerArgs must be a string array.`);
|
|
190
|
-
}
|
|
191
|
-
else {
|
|
192
|
-
sandbox.extraDockerArgs = args;
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
else {
|
|
196
|
-
errors.push(`${sourceLabel}: sandbox.extraDockerArgs must be a string array.`);
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
if (data.environmentInfo !== undefined) {
|
|
200
|
-
if (typeof data.environmentInfo === "string") {
|
|
201
|
-
sandbox.environmentInfo = data.environmentInfo;
|
|
202
|
-
}
|
|
203
|
-
else {
|
|
204
|
-
errors.push(`${sourceLabel}: sandbox.environmentInfo must be a string.`);
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
if (Object.keys(sandbox).length === 0) {
|
|
208
|
-
return { errors };
|
|
209
|
-
}
|
|
210
|
-
return { config: sandbox, errors };
|
|
191
|
+
const sandboxResult = parseOptionalFields(parsed.data, sourceLabel, [
|
|
192
|
+
["image", SandboxFieldsSchema.shape.image, "sandbox.image must be a non-empty string."],
|
|
193
|
+
[
|
|
194
|
+
"mountPath",
|
|
195
|
+
SandboxFieldsSchema.shape.mountPath,
|
|
196
|
+
"sandbox.mountPath must be a non-empty string.",
|
|
197
|
+
],
|
|
198
|
+
[
|
|
199
|
+
"pruneAfterHours",
|
|
200
|
+
SandboxFieldsSchema.shape.pruneAfterHours,
|
|
201
|
+
"sandbox.pruneAfterHours must be a positive number.",
|
|
202
|
+
],
|
|
203
|
+
[
|
|
204
|
+
"extraDockerArgs",
|
|
205
|
+
SandboxFieldsSchema.shape.extraDockerArgs,
|
|
206
|
+
"sandbox.extraDockerArgs must be a string array.",
|
|
207
|
+
],
|
|
208
|
+
[
|
|
209
|
+
"environmentInfo",
|
|
210
|
+
SandboxFieldsSchema.shape.environmentInfo,
|
|
211
|
+
"sandbox.environmentInfo must be a string.",
|
|
212
|
+
],
|
|
213
|
+
]);
|
|
214
|
+
if (Object.keys(sandboxResult.values).length === 0) {
|
|
215
|
+
return { errors: sandboxResult.errors };
|
|
216
|
+
}
|
|
217
|
+
return { config: sandboxResult.values, errors: sandboxResult.errors };
|
|
211
218
|
}
|
|
212
219
|
function parseAgentContextFiles(raw, sourceLabel) {
|
|
213
220
|
if (raw === undefined) {
|
|
214
221
|
return { paths: [], errors: [] };
|
|
215
222
|
}
|
|
216
|
-
|
|
223
|
+
const parsed = AgentContextFilesSchema.safeParse(raw);
|
|
224
|
+
if (!parsed.success) {
|
|
217
225
|
return {
|
|
218
226
|
paths: [],
|
|
219
227
|
errors: [`${sourceLabel}: 'agentContextFiles' must be a string array.`],
|
|
220
228
|
};
|
|
221
229
|
}
|
|
222
|
-
|
|
223
|
-
for (const entry of raw) {
|
|
224
|
-
if (typeof entry !== "string" || !entry.trim()) {
|
|
225
|
-
return {
|
|
226
|
-
paths: [],
|
|
227
|
-
errors: [`${sourceLabel}: 'agentContextFiles' must be a string array.`],
|
|
228
|
-
};
|
|
229
|
-
}
|
|
230
|
-
paths.push(entry.trim());
|
|
231
|
-
}
|
|
232
|
-
return { paths, errors: [] };
|
|
230
|
+
return { paths: parsed.data, errors: [] };
|
|
233
231
|
}
|
|
234
232
|
function parseSubagentsConfig(raw, sourceLabel) {
|
|
235
233
|
if (raw === undefined) {
|
|
236
234
|
return { errors: [] };
|
|
237
235
|
}
|
|
238
|
-
|
|
236
|
+
const parsed = SubagentsConfigSchema.safeParse(raw);
|
|
237
|
+
if (!parsed.success) {
|
|
238
|
+
if (parsed.error.issues.some((issue) => issue.path[0] === "defaultLaunchModels")) {
|
|
239
|
+
return { errors: [`${sourceLabel}: subagents.defaultLaunchModels must be a string array.`] };
|
|
240
|
+
}
|
|
239
241
|
return { errors: [`${sourceLabel}: 'subagents' must be an object.`] };
|
|
240
242
|
}
|
|
241
|
-
const
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
if ("defaultLaunchModels" in data) {
|
|
245
|
-
if (!Array.isArray(data.defaultLaunchModels)) {
|
|
246
|
-
errors.push(`${sourceLabel}: subagents.defaultLaunchModels must be a string array.`);
|
|
247
|
-
return { errors };
|
|
248
|
-
}
|
|
249
|
-
const launchModelsResult = parseSubagentLaunchModelList(data.defaultLaunchModels);
|
|
250
|
-
if (launchModelsResult.error) {
|
|
251
|
-
errors.push(`${sourceLabel}: subagents.defaultLaunchModels ${launchModelsResult.error}. expected <provider>/<model>:<effort>.`);
|
|
252
|
-
}
|
|
253
|
-
else {
|
|
254
|
-
config.defaultLaunchModels = launchModelsResult.launchModels;
|
|
255
|
-
}
|
|
243
|
+
const { defaultLaunchModels } = parsed.data;
|
|
244
|
+
if (defaultLaunchModels === undefined) {
|
|
245
|
+
return { errors: [] };
|
|
256
246
|
}
|
|
257
|
-
|
|
258
|
-
|
|
247
|
+
const launchModelsResult = parseSubagentLaunchModelList(defaultLaunchModels);
|
|
248
|
+
if (launchModelsResult.error) {
|
|
249
|
+
return {
|
|
250
|
+
errors: [
|
|
251
|
+
`${sourceLabel}: subagents.defaultLaunchModels ${launchModelsResult.error}. expected <provider>/<model>:<effort>.`,
|
|
252
|
+
],
|
|
253
|
+
};
|
|
259
254
|
}
|
|
260
|
-
return {
|
|
255
|
+
return {
|
|
256
|
+
config: {
|
|
257
|
+
defaultLaunchModels: launchModelsResult.launchModels,
|
|
258
|
+
},
|
|
259
|
+
errors: [],
|
|
260
|
+
};
|
|
261
261
|
}
|
|
262
262
|
function parseModelNoticeTarget(rawKey) {
|
|
263
263
|
const parsedKey = parseModelNoticeKey(rawKey);
|
|
@@ -282,113 +282,99 @@ function parseModelSystemNotices(raw, sourceLabel) {
|
|
|
282
282
|
if (raw === undefined) {
|
|
283
283
|
return { errors: [] };
|
|
284
284
|
}
|
|
285
|
-
|
|
285
|
+
const parsedRecord = StringRecordSchema.safeParse(raw);
|
|
286
|
+
if (!parsedRecord.success) {
|
|
286
287
|
return { errors: [`${sourceLabel}: 'modelSystemNotices' must be an object.`] };
|
|
287
288
|
}
|
|
288
|
-
const data = raw;
|
|
289
289
|
const notices = {};
|
|
290
290
|
const errors = [];
|
|
291
|
-
for (const [rawKey, rawValue] of Object.entries(data)) {
|
|
291
|
+
for (const [rawKey, rawValue] of Object.entries(parsedRecord.data)) {
|
|
292
292
|
const parsedKey = parseModelNoticeTarget(rawKey);
|
|
293
293
|
if (parsedKey.error || !parsedKey.normalizedKey) {
|
|
294
294
|
errors.push(`${sourceLabel}: modelSystemNotices.${rawKey} ${parsedKey.error ?? "is invalid"}.`);
|
|
295
295
|
continue;
|
|
296
296
|
}
|
|
297
|
-
|
|
297
|
+
const parsedNotice = NonEmptyStringSchema.safeParse(rawValue);
|
|
298
|
+
if (!parsedNotice.success) {
|
|
298
299
|
errors.push(`${sourceLabel}: modelSystemNotices.${rawKey} must be a non-empty string notice.`);
|
|
299
300
|
continue;
|
|
300
301
|
}
|
|
301
|
-
notices[parsedKey.normalizedKey] =
|
|
302
|
+
notices[parsedKey.normalizedKey] = parsedNotice.data;
|
|
302
303
|
}
|
|
303
304
|
if (Object.keys(notices).length === 0) {
|
|
304
305
|
return { errors };
|
|
305
306
|
}
|
|
306
307
|
return { notices, errors };
|
|
307
308
|
}
|
|
308
|
-
function isPositiveInteger(value) {
|
|
309
|
-
return (typeof value === "number" && Number.isInteger(value) && Number.isFinite(value) && value > 0);
|
|
310
|
-
}
|
|
311
|
-
function parsePositiveIntegerField(value, options) {
|
|
312
|
-
if (!isPositiveInteger(value)) {
|
|
313
|
-
return undefined;
|
|
314
|
-
}
|
|
315
|
-
if (options?.max !== undefined && value > options.max) {
|
|
316
|
-
return undefined;
|
|
317
|
-
}
|
|
318
|
-
return value;
|
|
319
|
-
}
|
|
320
309
|
function parseAsyncClientTarget(raw, sourceLabel, key) {
|
|
321
|
-
|
|
310
|
+
const parsedRecord = StringRecordSchema.safeParse(raw);
|
|
311
|
+
if (!parsedRecord.success) {
|
|
322
312
|
return { errors: [`${sourceLabel}: async.client.targets.${key} must be an object.`] };
|
|
323
313
|
}
|
|
324
|
-
const
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
}
|
|
345
|
-
if (errors.length > 0) {
|
|
346
|
-
return { errors };
|
|
347
|
-
}
|
|
348
|
-
return { config, errors: [] };
|
|
314
|
+
const parsedTarget = AsyncClientTargetSchema.safeParse(parsedRecord.data);
|
|
315
|
+
if (parsedTarget.success) {
|
|
316
|
+
return { config: parsedTarget.data, errors: [] };
|
|
317
|
+
}
|
|
318
|
+
const errors = new Set();
|
|
319
|
+
for (const issue of parsedTarget.error.issues) {
|
|
320
|
+
switch (issue.path[0]) {
|
|
321
|
+
case "url":
|
|
322
|
+
errors.add(`${sourceLabel}: async.client.targets.${key}.url must be a non-empty string.`);
|
|
323
|
+
break;
|
|
324
|
+
case "token":
|
|
325
|
+
errors.add(`${sourceLabel}: async.client.targets.${key}.token must be a non-empty string.`);
|
|
326
|
+
break;
|
|
327
|
+
case "timeoutMs":
|
|
328
|
+
errors.add(`${sourceLabel}: async.client.targets.${key}.timeoutMs must be a positive integer.`);
|
|
329
|
+
break;
|
|
330
|
+
default:
|
|
331
|
+
break;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
return { errors: [...errors] };
|
|
349
335
|
}
|
|
350
336
|
function parseAsyncClientConfig(raw, sourceLabel) {
|
|
351
337
|
if (raw === undefined) {
|
|
352
338
|
return { errors: [] };
|
|
353
339
|
}
|
|
354
|
-
|
|
340
|
+
const parsedRecord = StringRecordSchema.safeParse(raw);
|
|
341
|
+
if (!parsedRecord.success) {
|
|
355
342
|
return { errors: [`${sourceLabel}: async.client must be an object.`] };
|
|
356
343
|
}
|
|
357
|
-
const data =
|
|
358
|
-
const errors = [];
|
|
344
|
+
const data = parsedRecord.data;
|
|
359
345
|
const config = {};
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
}
|
|
375
|
-
}
|
|
346
|
+
const scalarResult = parseOptionalFields(data, sourceLabel, [
|
|
347
|
+
[
|
|
348
|
+
"defaultTarget",
|
|
349
|
+
NonEmptyStringSchema,
|
|
350
|
+
"async.client.defaultTarget must be a non-empty string.",
|
|
351
|
+
],
|
|
352
|
+
[
|
|
353
|
+
"defaultProjectId",
|
|
354
|
+
NonEmptyStringSchema,
|
|
355
|
+
"async.client.defaultProjectId must be a non-empty string.",
|
|
356
|
+
],
|
|
357
|
+
]);
|
|
358
|
+
Object.assign(config, scalarResult.values);
|
|
359
|
+
const errors = [...scalarResult.errors];
|
|
376
360
|
if (data.targets !== undefined) {
|
|
377
|
-
|
|
361
|
+
const parsedTargetsRecord = StringRecordSchema.safeParse(data.targets);
|
|
362
|
+
if (!parsedTargetsRecord.success) {
|
|
378
363
|
errors.push(`${sourceLabel}: async.client.targets must be an object.`);
|
|
379
364
|
}
|
|
380
365
|
else {
|
|
381
366
|
const targets = {};
|
|
382
|
-
for (const [key, value] of Object.entries(data
|
|
383
|
-
|
|
367
|
+
for (const [key, value] of Object.entries(parsedTargetsRecord.data)) {
|
|
368
|
+
const parsedTargetKey = NonEmptyStringSchema.safeParse(key);
|
|
369
|
+
if (!parsedTargetKey.success) {
|
|
384
370
|
errors.push(`${sourceLabel}: async.client.targets keys must be non-empty.`);
|
|
385
371
|
continue;
|
|
386
372
|
}
|
|
387
|
-
const
|
|
388
|
-
if (
|
|
389
|
-
targets[key] =
|
|
373
|
+
const parsedTarget = parseAsyncClientTarget(value, sourceLabel, key);
|
|
374
|
+
if (parsedTarget.config) {
|
|
375
|
+
targets[key] = parsedTarget.config;
|
|
390
376
|
}
|
|
391
|
-
errors.push(...
|
|
377
|
+
errors.push(...parsedTarget.errors);
|
|
392
378
|
}
|
|
393
379
|
if (Object.keys(targets).length > 0) {
|
|
394
380
|
config.targets = targets;
|
|
@@ -404,10 +390,11 @@ function parseAsyncConfig(raw, sourceLabel) {
|
|
|
404
390
|
if (raw === undefined) {
|
|
405
391
|
return { errors: [] };
|
|
406
392
|
}
|
|
407
|
-
|
|
393
|
+
const parsedRecord = StringRecordSchema.safeParse(raw);
|
|
394
|
+
if (!parsedRecord.success) {
|
|
408
395
|
return { errors: [`${sourceLabel}: 'async' must be an object.`] };
|
|
409
396
|
}
|
|
410
|
-
const data =
|
|
397
|
+
const data = parsedRecord.data;
|
|
411
398
|
const config = {};
|
|
412
399
|
const errors = [];
|
|
413
400
|
const clientResult = parseAsyncClientConfig(data.client, sourceLabel);
|
|
@@ -415,11 +402,12 @@ function parseAsyncConfig(raw, sourceLabel) {
|
|
|
415
402
|
config.client = clientResult.config;
|
|
416
403
|
}
|
|
417
404
|
errors.push(...clientResult.errors);
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
405
|
+
const movedAsyncFields = ["server", "projects"];
|
|
406
|
+
for (const field of movedAsyncFields) {
|
|
407
|
+
if (data[field] === undefined) {
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
errors.push(`${sourceLabel}: async.${field} was moved to daemon config file. use 'tau async daemon --config-file <path>'.`);
|
|
423
411
|
}
|
|
424
412
|
if (Object.keys(config).length === 0) {
|
|
425
413
|
return { errors };
|
|
@@ -448,16 +436,7 @@ function loadConfigFile(level, deps, sourceLabel) {
|
|
|
448
436
|
};
|
|
449
437
|
}
|
|
450
438
|
}
|
|
451
|
-
function
|
|
452
|
-
if (!target && !overlay) {
|
|
453
|
-
return undefined;
|
|
454
|
-
}
|
|
455
|
-
return {
|
|
456
|
-
...(target ?? {}),
|
|
457
|
-
...(overlay ?? {}),
|
|
458
|
-
};
|
|
459
|
-
}
|
|
460
|
-
function mergeSandboxConfig(target, overlay) {
|
|
439
|
+
function mergeOptionalObject(target, overlay) {
|
|
461
440
|
if (!target && !overlay) {
|
|
462
441
|
return undefined;
|
|
463
442
|
}
|
|
@@ -478,24 +457,6 @@ function mergeSubagentsConfig(target, overlay) {
|
|
|
478
457
|
}
|
|
479
458
|
return merged;
|
|
480
459
|
}
|
|
481
|
-
function mergeModelSystemNotices(target, overlay) {
|
|
482
|
-
if (!target && !overlay) {
|
|
483
|
-
return undefined;
|
|
484
|
-
}
|
|
485
|
-
return {
|
|
486
|
-
...(target ?? {}),
|
|
487
|
-
...(overlay ?? {}),
|
|
488
|
-
};
|
|
489
|
-
}
|
|
490
|
-
function mergeAsyncClientTarget(target, overlay) {
|
|
491
|
-
if (!target && !overlay) {
|
|
492
|
-
return undefined;
|
|
493
|
-
}
|
|
494
|
-
return {
|
|
495
|
-
...(target ?? {}),
|
|
496
|
-
...(overlay ?? {}),
|
|
497
|
-
};
|
|
498
|
-
}
|
|
499
460
|
function mergeAsyncClientConfig(target, overlay) {
|
|
500
461
|
if (!target && !overlay) {
|
|
501
462
|
return undefined;
|
|
@@ -515,7 +476,7 @@ function mergeAsyncClientConfig(target, overlay) {
|
|
|
515
476
|
targets.set(key, { ...value });
|
|
516
477
|
}
|
|
517
478
|
for (const [key, value] of Object.entries(overlay?.targets ?? {})) {
|
|
518
|
-
targets.set(key,
|
|
479
|
+
targets.set(key, mergeOptionalObject(targets.get(key), value) ?? value);
|
|
519
480
|
}
|
|
520
481
|
if (targets.size > 0) {
|
|
521
482
|
merged.targets = Object.fromEntries(targets.entries());
|
|
@@ -534,26 +495,6 @@ function mergeAsyncConfig(target, overlay) {
|
|
|
534
495
|
};
|
|
535
496
|
return merged;
|
|
536
497
|
}
|
|
537
|
-
function resolveAsyncConfig(_level, config) {
|
|
538
|
-
return {
|
|
539
|
-
...config,
|
|
540
|
-
...(config.client
|
|
541
|
-
? {
|
|
542
|
-
client: {
|
|
543
|
-
...config.client,
|
|
544
|
-
...(config.client.targets
|
|
545
|
-
? {
|
|
546
|
-
targets: Object.fromEntries(Object.entries(config.client.targets).map(([key, value]) => [
|
|
547
|
-
key,
|
|
548
|
-
{ ...value },
|
|
549
|
-
])),
|
|
550
|
-
}
|
|
551
|
-
: {}),
|
|
552
|
-
},
|
|
553
|
-
}
|
|
554
|
-
: {}),
|
|
555
|
-
};
|
|
556
|
-
}
|
|
557
498
|
function resolveAgentContextPaths(level, rawPaths) {
|
|
558
499
|
const root = level.levelRoot;
|
|
559
500
|
return rawPaths.map((entry) => resolve(root, entry));
|
|
@@ -585,11 +526,11 @@ function mergeConfigLevels(levels, configs) {
|
|
|
585
526
|
for (let i = 0; i < levels.length; i += 1) {
|
|
586
527
|
const level = levels[i];
|
|
587
528
|
const config = configs[i] ?? {};
|
|
588
|
-
apiKeys =
|
|
589
|
-
sandbox =
|
|
529
|
+
apiKeys = mergeOptionalObject(apiKeys, config.apiKeys);
|
|
530
|
+
sandbox = mergeOptionalObject(sandbox, config.sandbox);
|
|
590
531
|
subagents = mergeSubagentsConfig(subagents, config.subagents);
|
|
591
|
-
modelSystemNotices =
|
|
592
|
-
asyncConfig = mergeAsyncConfig(asyncConfig, config.async
|
|
532
|
+
modelSystemNotices = mergeOptionalObject(modelSystemNotices, config.modelSystemNotices);
|
|
533
|
+
asyncConfig = mergeAsyncConfig(asyncConfig, config.async);
|
|
593
534
|
if (config.defaultPersona !== undefined) {
|
|
594
535
|
merged.defaultPersona = config.defaultPersona;
|
|
595
536
|
}
|