@matheuskrumenauer/tanya 0.2.0-beta.0 → 0.4.0-beta.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.
- package/README.md +31 -0
- package/dist/cli.js +1678 -577
- package/dist/cli.js.map +1 -1
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import { existsSync as
|
|
5
|
-
import { join as
|
|
4
|
+
import { existsSync as existsSync17, readFileSync as readFileSync8 } from "fs";
|
|
5
|
+
import { join as join22, resolve as resolve12 } from "path";
|
|
6
6
|
import { Command, Option } from "commander";
|
|
7
7
|
|
|
8
8
|
// src/config/env.ts
|
|
@@ -40,6 +40,170 @@ function numberEnvValue(local, key, fallback) {
|
|
|
40
40
|
return Number.isFinite(parsed) ? parsed : fallback;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
// src/providers/adapters/deepseek.ts
|
|
44
|
+
var deepSeekAdapter = {
|
|
45
|
+
id: "deepseek",
|
|
46
|
+
matchBaseUrl: /api\.deepseek\.com/i,
|
|
47
|
+
defaultBaseUrl: "https://api.deepseek.com",
|
|
48
|
+
defaultModel: "deepseek-chat",
|
|
49
|
+
capabilities: {
|
|
50
|
+
toolChoiceRequired: false,
|
|
51
|
+
parallelToolCalls: false,
|
|
52
|
+
jsonMode: true,
|
|
53
|
+
vision: false,
|
|
54
|
+
reasoning: true,
|
|
55
|
+
flattenSchemas: false
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// src/providers/adapters/types.ts
|
|
60
|
+
function withoutUnsupportedToolChoice(req) {
|
|
61
|
+
if (req.tool_choice === "required") {
|
|
62
|
+
const { tool_choice: _toolChoice, ...rest } = req;
|
|
63
|
+
return rest;
|
|
64
|
+
}
|
|
65
|
+
return req;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// src/providers/adapters/grok.ts
|
|
69
|
+
var grokAdapter = {
|
|
70
|
+
id: "grok",
|
|
71
|
+
matchBaseUrl: /(?:api\.)?x\.ai/i,
|
|
72
|
+
defaultBaseUrl: "https://api.x.ai/v1",
|
|
73
|
+
defaultModel: "grok-3-mini",
|
|
74
|
+
capabilities: {
|
|
75
|
+
toolChoiceRequired: false,
|
|
76
|
+
parallelToolCalls: false,
|
|
77
|
+
jsonMode: true,
|
|
78
|
+
vision: true,
|
|
79
|
+
reasoning: true,
|
|
80
|
+
flattenSchemas: false
|
|
81
|
+
},
|
|
82
|
+
preRequest: withoutUnsupportedToolChoice
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// src/providers/adapters/groq.ts
|
|
86
|
+
var groqAdapter = {
|
|
87
|
+
id: "groq",
|
|
88
|
+
matchBaseUrl: /api\.groq\.com/i,
|
|
89
|
+
defaultBaseUrl: "https://api.groq.com/openai/v1",
|
|
90
|
+
defaultModel: "llama-3.3-70b-versatile",
|
|
91
|
+
capabilities: {
|
|
92
|
+
toolChoiceRequired: false,
|
|
93
|
+
parallelToolCalls: false,
|
|
94
|
+
jsonMode: true,
|
|
95
|
+
vision: false,
|
|
96
|
+
reasoning: false,
|
|
97
|
+
flattenSchemas: false
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
// src/providers/adapters/ollama.ts
|
|
102
|
+
var ollamaAdapter = {
|
|
103
|
+
id: "ollama",
|
|
104
|
+
matchBaseUrl: /(?:localhost:11434|127\.0\.0\.1:11434|ollama)/i,
|
|
105
|
+
defaultBaseUrl: "http://localhost:11434/v1",
|
|
106
|
+
defaultModel: "qwen2.5-coder:7b",
|
|
107
|
+
capabilities: {
|
|
108
|
+
toolChoiceRequired: false,
|
|
109
|
+
parallelToolCalls: false,
|
|
110
|
+
jsonMode: true,
|
|
111
|
+
vision: false,
|
|
112
|
+
reasoning: false,
|
|
113
|
+
flattenSchemas: false
|
|
114
|
+
},
|
|
115
|
+
preRequest: withoutUnsupportedToolChoice
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// src/providers/adapters/openai.ts
|
|
119
|
+
var openAiAdapter = {
|
|
120
|
+
id: "openai",
|
|
121
|
+
matchBaseUrl: /(?:api\.)?openai\.com/i,
|
|
122
|
+
defaultBaseUrl: "https://api.openai.com/v1",
|
|
123
|
+
defaultModel: "gpt-4.1-mini",
|
|
124
|
+
capabilities: {
|
|
125
|
+
toolChoiceRequired: true,
|
|
126
|
+
parallelToolCalls: true,
|
|
127
|
+
jsonMode: true,
|
|
128
|
+
vision: true,
|
|
129
|
+
reasoning: true,
|
|
130
|
+
flattenSchemas: false
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
// src/providers/adapters/qwen.ts
|
|
135
|
+
var qwenAdapter = {
|
|
136
|
+
id: "qwen",
|
|
137
|
+
matchBaseUrl: /(?:dashscope|aliyuncs|qwen)/i,
|
|
138
|
+
defaultBaseUrl: "https://dashscope.aliyuncs.com/compatible-mode/v1",
|
|
139
|
+
defaultModel: "qwen3-coder-plus",
|
|
140
|
+
capabilities: {
|
|
141
|
+
toolChoiceRequired: false,
|
|
142
|
+
parallelToolCalls: false,
|
|
143
|
+
jsonMode: true,
|
|
144
|
+
vision: true,
|
|
145
|
+
reasoning: true,
|
|
146
|
+
flattenSchemas: true
|
|
147
|
+
},
|
|
148
|
+
preRequest: (req) => ({
|
|
149
|
+
...withoutUnsupportedToolChoice(req),
|
|
150
|
+
parallel_tool_calls: false
|
|
151
|
+
})
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
// src/providers/adapters/together.ts
|
|
155
|
+
var togetherAdapter = {
|
|
156
|
+
id: "together",
|
|
157
|
+
matchBaseUrl: /api\.together\.xyz/i,
|
|
158
|
+
defaultBaseUrl: "https://api.together.xyz/v1",
|
|
159
|
+
defaultModel: "Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8",
|
|
160
|
+
capabilities: {
|
|
161
|
+
toolChoiceRequired: false,
|
|
162
|
+
parallelToolCalls: false,
|
|
163
|
+
jsonMode: true,
|
|
164
|
+
vision: false,
|
|
165
|
+
reasoning: false,
|
|
166
|
+
flattenSchemas: true
|
|
167
|
+
},
|
|
168
|
+
preRequest: withoutUnsupportedToolChoice
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
// src/providers/adapters/index.ts
|
|
172
|
+
var providerAdapters = [
|
|
173
|
+
deepSeekAdapter,
|
|
174
|
+
qwenAdapter,
|
|
175
|
+
grokAdapter,
|
|
176
|
+
groqAdapter,
|
|
177
|
+
togetherAdapter,
|
|
178
|
+
ollamaAdapter,
|
|
179
|
+
openAiAdapter
|
|
180
|
+
];
|
|
181
|
+
var aliases = /* @__PURE__ */ new Map([
|
|
182
|
+
["deepseek-reasoner", "deepseek"],
|
|
183
|
+
["deepseek-chat", "deepseek"],
|
|
184
|
+
["xai", "grok"],
|
|
185
|
+
["openai-compatible", "openai"],
|
|
186
|
+
["custom", "openai"]
|
|
187
|
+
]);
|
|
188
|
+
function resolveProviderAdapter(input = {}) {
|
|
189
|
+
const provider = normalizeProviderId(input.provider);
|
|
190
|
+
if (provider) {
|
|
191
|
+
const explicit = providerAdapters.find((adapter) => adapter.id === provider);
|
|
192
|
+
if (explicit) return explicit;
|
|
193
|
+
}
|
|
194
|
+
const baseUrl = input.baseUrl?.trim();
|
|
195
|
+
if (baseUrl) {
|
|
196
|
+
const matched = providerAdapters.find((adapter) => adapter.matchBaseUrl?.test(baseUrl));
|
|
197
|
+
if (matched) return matched;
|
|
198
|
+
}
|
|
199
|
+
return openAiAdapter;
|
|
200
|
+
}
|
|
201
|
+
function normalizeProviderId(provider) {
|
|
202
|
+
const normalized = provider?.trim().toLowerCase();
|
|
203
|
+
if (!normalized) return null;
|
|
204
|
+
return aliases.get(normalized) ?? normalized;
|
|
205
|
+
}
|
|
206
|
+
|
|
43
207
|
// src/config/env.ts
|
|
44
208
|
function loadDotEnv(cwd) {
|
|
45
209
|
const envPath = join(cwd, ".env");
|
|
@@ -48,11 +212,18 @@ function loadDotEnv(cwd) {
|
|
|
48
212
|
}
|
|
49
213
|
function loadConfig(cwd = process.cwd()) {
|
|
50
214
|
const local = loadDotEnv(cwd);
|
|
51
|
-
const provider = envValue(local, "TANYA_PROVIDER") === "custom" ? "custom" : "deepseek";
|
|
52
215
|
const profile = envValue(local, "TANYA_PROFILE") === "reasoner" ? "reasoner" : "chat";
|
|
216
|
+
const requestedProvider = envValue(local, "TANYA_PROVIDER").trim();
|
|
217
|
+
const requestedBaseUrl = envValue(local, "TANYA_BASE_URL").trim();
|
|
218
|
+
const providerSeed = requestedProvider || (requestedBaseUrl ? "" : "deepseek");
|
|
219
|
+
const adapter = resolveProviderAdapter({
|
|
220
|
+
provider: providerSeed,
|
|
221
|
+
baseUrl: requestedBaseUrl
|
|
222
|
+
});
|
|
223
|
+
const provider = providerSeed || adapter.id;
|
|
53
224
|
const apiKey = provider === "deepseek" ? envValue(local, "DEEPSEEK_API_KEY") || envValue(local, "TANYA_API_KEY") : envValue(local, "TANYA_API_KEY");
|
|
54
|
-
const baseUrl = provider === "deepseek" ? envValue(local, "DEEPSEEK_BASE_URL") || envValue(local, "TANYA_BASE_URL") || "https://api.deepseek.com" : envValue(local, "TANYA_BASE_URL");
|
|
55
|
-
const profileModelDefault = profile === "reasoner" ? "deepseek-reasoner" : "deepseek-chat";
|
|
225
|
+
const baseUrl = provider === "deepseek" ? envValue(local, "DEEPSEEK_BASE_URL") || envValue(local, "TANYA_BASE_URL") || adapter.defaultBaseUrl || "https://api.deepseek.com" : envValue(local, "TANYA_BASE_URL") || adapter.defaultBaseUrl || "";
|
|
226
|
+
const profileModelDefault = profile === "reasoner" && adapter.id === "deepseek" ? "deepseek-reasoner" : adapter.defaultModel ?? "deepseek-chat";
|
|
56
227
|
const model = envValue(local, "TANYA_MODEL") || profileModelDefault;
|
|
57
228
|
const profileTimeoutDefault = profile === "reasoner" ? 18e4 : 9e4;
|
|
58
229
|
const timeoutMs = numberEnvValue(local, "TANYA_TIMEOUT_MS", profileTimeoutDefault);
|
|
@@ -144,12 +315,12 @@ function buildAutoBriefBlock(metadata) {
|
|
|
144
315
|
}
|
|
145
316
|
return lines;
|
|
146
317
|
}
|
|
147
|
-
function normalizeRunContext(
|
|
148
|
-
if (!isRecord(
|
|
149
|
-
const taskInput = isRecord(
|
|
150
|
-
const artifactsInput = Array.isArray(
|
|
151
|
-
const verificationInput = isRecord(
|
|
152
|
-
const contextFilesInput = Array.isArray(
|
|
318
|
+
function normalizeRunContext(input) {
|
|
319
|
+
if (!isRecord(input)) throw new Error("Context file must contain a JSON object.");
|
|
320
|
+
const taskInput = isRecord(input.task) ? input.task : void 0;
|
|
321
|
+
const artifactsInput = Array.isArray(input.artifacts) ? input.artifacts : void 0;
|
|
322
|
+
const verificationInput = isRecord(input.verification) ? input.verification : void 0;
|
|
323
|
+
const contextFilesInput = Array.isArray(input.contextFiles) ? input.contextFiles : void 0;
|
|
153
324
|
const artifacts = artifactsInput?.filter(isRecord).map((artifact) => ({
|
|
154
325
|
path: asString(artifact.path) ?? "",
|
|
155
326
|
...asString(artifact.sourcePath) ? { sourcePath: asString(artifact.sourcePath) } : {},
|
|
@@ -170,10 +341,10 @@ function normalizeRunContext(input2) {
|
|
|
170
341
|
...asString(taskInput.summary) ? { summary: asString(taskInput.summary) } : {}
|
|
171
342
|
} : void 0;
|
|
172
343
|
const verificationCommands = verificationInput ? stringArray(verificationInput.commands) : void 0;
|
|
173
|
-
const instructions = stringArray(
|
|
174
|
-
const languages = stringArray(
|
|
175
|
-
const frameworks = stringArray(
|
|
176
|
-
const stack = asString(
|
|
344
|
+
const instructions = stringArray(input.instructions);
|
|
345
|
+
const languages = stringArray(input.languages);
|
|
346
|
+
const frameworks = stringArray(input.frameworks);
|
|
347
|
+
const stack = asString(input.stack);
|
|
177
348
|
return {
|
|
178
349
|
...task && Object.keys(task).length > 0 ? { task } : {},
|
|
179
350
|
...artifacts?.length ? { artifacts } : {},
|
|
@@ -183,8 +354,8 @@ function normalizeRunContext(input2) {
|
|
|
183
354
|
...languages ? { languages } : {},
|
|
184
355
|
...frameworks ? { frameworks } : {},
|
|
185
356
|
...stack ? { stack } : {},
|
|
186
|
-
...isRecord(
|
|
187
|
-
...isRecord(
|
|
357
|
+
...isRecord(input.expected_report) ? { expected_report: input.expected_report } : {},
|
|
358
|
+
...isRecord(input.metadata) ? { metadata: input.metadata } : {}
|
|
188
359
|
};
|
|
189
360
|
}
|
|
190
361
|
function loadRunContextFile(path) {
|
|
@@ -281,12 +452,12 @@ function relativeMaterializedPath(root, artifact, source) {
|
|
|
281
452
|
}
|
|
282
453
|
return basename(source);
|
|
283
454
|
}
|
|
284
|
-
function materializeCliArtifacts(
|
|
285
|
-
const artifactInputs = unique(
|
|
286
|
-
const contextInputs = unique(
|
|
287
|
-
if (artifactInputs.length === 0 && contextInputs.length === 0 && !
|
|
288
|
-
const cwd = resolve2(
|
|
289
|
-
const root =
|
|
455
|
+
function materializeCliArtifacts(input) {
|
|
456
|
+
const artifactInputs = unique(input.artifacts);
|
|
457
|
+
const contextInputs = unique(input.contextPaths ?? []);
|
|
458
|
+
if (artifactInputs.length === 0 && contextInputs.length === 0 && !input.artifactOutputRoot) return input.baseContext;
|
|
459
|
+
const cwd = resolve2(input.cwd);
|
|
460
|
+
const root = input.root ? resolve2(input.root) : void 0;
|
|
290
461
|
const targetRoot = resolve2(cwd, ".tania", "artifacts");
|
|
291
462
|
const contextTargetRoot = resolve2(cwd, ".tania", "context");
|
|
292
463
|
const materialized = [];
|
|
@@ -366,13 +537,13 @@ function materializeCliArtifacts(input2) {
|
|
|
366
537
|
"utf8"
|
|
367
538
|
);
|
|
368
539
|
}
|
|
369
|
-
const artifactOutputRoot =
|
|
540
|
+
const artifactOutputRoot = input.artifactOutputRoot ? resolve2(input.artifactOutputRoot) : void 0;
|
|
370
541
|
return {
|
|
371
|
-
...
|
|
372
|
-
artifacts: [...
|
|
373
|
-
contextFiles: [...
|
|
542
|
+
...input.baseContext ?? {},
|
|
543
|
+
artifacts: [...input.baseContext?.artifacts ?? [], ...materialized],
|
|
544
|
+
contextFiles: [...input.baseContext?.contextFiles ?? [], ...contextFiles],
|
|
374
545
|
instructions: [
|
|
375
|
-
...
|
|
546
|
+
...input.baseContext?.instructions ?? [],
|
|
376
547
|
...materialized.length > 0 ? [
|
|
377
548
|
"Caller artifacts were provided through Tanya's artifact input contract and materialized under .tania/artifacts.",
|
|
378
549
|
"Read relevant materialized artifact paths before implementing related code."
|
|
@@ -387,14 +558,14 @@ function materializeCliArtifacts(input2) {
|
|
|
387
558
|
"When reporting artifact reuse, use the artifact sourcePath label when available."
|
|
388
559
|
],
|
|
389
560
|
expected_report: {
|
|
390
|
-
...
|
|
561
|
+
...input.baseContext?.expected_report ?? {},
|
|
391
562
|
artifact_reuse: true
|
|
392
563
|
},
|
|
393
564
|
metadata: {
|
|
394
|
-
...
|
|
565
|
+
...input.baseContext?.metadata ?? {},
|
|
395
566
|
...artifactOutputRoot ? { artifactOutputRoot } : {},
|
|
396
567
|
tanyaMaterializedContext: true,
|
|
397
|
-
keepMaterializedContext:
|
|
568
|
+
keepMaterializedContext: input.keepContext === true
|
|
398
569
|
}
|
|
399
570
|
};
|
|
400
571
|
}
|
|
@@ -471,6 +642,50 @@ function createCosmoSink(stream = process.stdout) {
|
|
|
471
642
|
partialOutput: event.partialOutput
|
|
472
643
|
});
|
|
473
644
|
break;
|
|
645
|
+
case "command_invoked":
|
|
646
|
+
ensureEventLine();
|
|
647
|
+
writeEvent(stream, {
|
|
648
|
+
t: "status",
|
|
649
|
+
message: `Command invoked: /${event.name}`,
|
|
650
|
+
key: `tanya:command:${event.name}`,
|
|
651
|
+
command: event.name,
|
|
652
|
+
args: event.args,
|
|
653
|
+
runId: event.runId
|
|
654
|
+
});
|
|
655
|
+
break;
|
|
656
|
+
case "tool_call_parse_warning":
|
|
657
|
+
ensureEventLine();
|
|
658
|
+
writeEvent(stream, {
|
|
659
|
+
t: "status",
|
|
660
|
+
message: `Tool-call parse warning: ${event.reason}`,
|
|
661
|
+
key: `tanya:tool-call-parse-warning:${event.toolCallId ?? event.turn ?? "unknown"}`,
|
|
662
|
+
provider: event.provider,
|
|
663
|
+
tool: event.tool,
|
|
664
|
+
attempt: event.attempt
|
|
665
|
+
});
|
|
666
|
+
break;
|
|
667
|
+
case "schema_flatten_warning":
|
|
668
|
+
ensureEventLine();
|
|
669
|
+
writeEvent(stream, {
|
|
670
|
+
t: "status",
|
|
671
|
+
message: `Schema flatten warning: ${event.reason}`,
|
|
672
|
+
key: `tanya:schema-flatten-warning:${event.tool ?? event.path}`,
|
|
673
|
+
provider: event.provider,
|
|
674
|
+
tool: event.tool,
|
|
675
|
+
path: event.path
|
|
676
|
+
});
|
|
677
|
+
break;
|
|
678
|
+
case "provider_throttle":
|
|
679
|
+
ensureEventLine();
|
|
680
|
+
writeEvent(stream, {
|
|
681
|
+
t: "status",
|
|
682
|
+
message: `Provider throttle: waiting ${Math.ceil(event.waitMs / 1e3)}s before retry ${event.attempt}.`,
|
|
683
|
+
key: `tanya:provider-throttle:${event.provider}:${event.attempt}`,
|
|
684
|
+
provider: event.provider,
|
|
685
|
+
attempt: event.attempt,
|
|
686
|
+
waitMs: event.waitMs
|
|
687
|
+
});
|
|
688
|
+
break;
|
|
474
689
|
case "final":
|
|
475
690
|
ensureEventLine();
|
|
476
691
|
if (event.message.trim()) {
|
|
@@ -520,6 +735,10 @@ var knownEventTypes = /* @__PURE__ */ new Set([
|
|
|
520
735
|
"tool_cancel_requested",
|
|
521
736
|
"tool_cancelled",
|
|
522
737
|
"tool_result",
|
|
738
|
+
"tool_call_parse_warning",
|
|
739
|
+
"schema_flatten_warning",
|
|
740
|
+
"provider_throttle",
|
|
741
|
+
"command_invoked",
|
|
523
742
|
"final",
|
|
524
743
|
"error"
|
|
525
744
|
]);
|
|
@@ -536,27 +755,237 @@ function createJsonlSink(stream = process.stdout) {
|
|
|
536
755
|
};
|
|
537
756
|
}
|
|
538
757
|
|
|
539
|
-
// src/providers/
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
758
|
+
// src/providers/schemaFlatten.ts
|
|
759
|
+
function flattenJsonSchema(schema) {
|
|
760
|
+
const warnings = [];
|
|
761
|
+
const root = clone(schema);
|
|
762
|
+
const flattened = flattenValue(root, root, "#", warnings, /* @__PURE__ */ new Set());
|
|
763
|
+
return { schema: flattened, warnings };
|
|
544
764
|
}
|
|
545
|
-
function
|
|
765
|
+
function flattenToolDefinition(tool) {
|
|
766
|
+
const result = flattenJsonSchema(tool.function.parameters);
|
|
767
|
+
return {
|
|
768
|
+
schema: {
|
|
769
|
+
...tool,
|
|
770
|
+
function: {
|
|
771
|
+
...tool.function,
|
|
772
|
+
parameters: result.schema
|
|
773
|
+
}
|
|
774
|
+
},
|
|
775
|
+
warnings: result.warnings.map((warning) => ({ ...warning, tool: tool.function.name }))
|
|
776
|
+
};
|
|
777
|
+
}
|
|
778
|
+
function flattenToolDefinitions(tools) {
|
|
779
|
+
const flattened = [];
|
|
780
|
+
const warnings = [];
|
|
781
|
+
for (const tool of tools) {
|
|
782
|
+
const result = flattenToolDefinition(tool);
|
|
783
|
+
flattened.push(result.schema);
|
|
784
|
+
warnings.push(...result.warnings);
|
|
785
|
+
}
|
|
786
|
+
return { schema: flattened, warnings };
|
|
787
|
+
}
|
|
788
|
+
function flattenValue(value, root, path, warnings, seenRefs) {
|
|
789
|
+
if (Array.isArray(value)) {
|
|
790
|
+
return value.map((item, index) => flattenValue(item, root, `${path}/${index}`, warnings, seenRefs));
|
|
791
|
+
}
|
|
792
|
+
if (!isObject(value)) return value;
|
|
793
|
+
const ref = typeof value.$ref === "string" ? value.$ref : null;
|
|
794
|
+
if (ref) {
|
|
795
|
+
const resolved = resolveLocalRef(root, ref);
|
|
796
|
+
if (resolved === void 0) {
|
|
797
|
+
warnings.push({ path, reason: `unresolved $ref ${ref}; leaving reference in place` });
|
|
798
|
+
return value;
|
|
799
|
+
}
|
|
800
|
+
if (seenRefs.has(ref)) {
|
|
801
|
+
warnings.push({ path, reason: `circular $ref ${ref}; leaving reference in place` });
|
|
802
|
+
return value;
|
|
803
|
+
}
|
|
804
|
+
warnings.push({ path, reason: `inlined $ref ${ref}` });
|
|
805
|
+
seenRefs.add(ref);
|
|
806
|
+
const flattened = flattenValue(resolved, root, path, warnings, seenRefs);
|
|
807
|
+
seenRefs.delete(ref);
|
|
808
|
+
const { $ref: _ref, ...overrides } = value;
|
|
809
|
+
return isObject(flattened) ? flattenObject({ ...flattened, ...overrides }, root, path, warnings, seenRefs) : flattened;
|
|
810
|
+
}
|
|
811
|
+
return flattenObject(value, root, path, warnings, seenRefs);
|
|
812
|
+
}
|
|
813
|
+
function flattenObject(object, root, path, warnings, seenRefs) {
|
|
814
|
+
const withoutDefs = stripDefinitions(object);
|
|
815
|
+
if (Array.isArray(withoutDefs.oneOf)) {
|
|
816
|
+
return flattenOneOf(withoutDefs, root, path, warnings, seenRefs);
|
|
817
|
+
}
|
|
818
|
+
const output = {};
|
|
819
|
+
for (const [key, raw] of Object.entries(withoutDefs)) {
|
|
820
|
+
output[key] = flattenValue(raw, root, `${path}/${escapePointer(key)}`, warnings, seenRefs);
|
|
821
|
+
}
|
|
822
|
+
return output;
|
|
823
|
+
}
|
|
824
|
+
function flattenOneOf(object, root, path, warnings, seenRefs) {
|
|
825
|
+
const variants = object.oneOf;
|
|
826
|
+
const flattenedVariants = variants.map((variant, index) => flattenValue(variant, root, `${path}/oneOf/${index}`, warnings, seenRefs)).filter(isObject);
|
|
827
|
+
if (flattenedVariants.length === 0) {
|
|
828
|
+
warnings.push({ path, reason: "oneOf had no object variants; dropped oneOf" });
|
|
829
|
+
const { oneOf: _oneOf2, ...rest2 } = object;
|
|
830
|
+
return flattenObject(rest2, root, path, warnings, seenRefs);
|
|
831
|
+
}
|
|
832
|
+
const commonType = commonScalar(flattenedVariants.map((variant) => variant.type));
|
|
833
|
+
const commonProperties = commonObjectProperties(flattenedVariants);
|
|
834
|
+
const commonRequired = commonRequiredFields(flattenedVariants);
|
|
835
|
+
const { oneOf: _oneOf, ...rest } = object;
|
|
836
|
+
warnings.push({ path, reason: `collapsed oneOf (${variants.length} variants) to common object shape` });
|
|
837
|
+
return flattenObject({
|
|
838
|
+
...rest,
|
|
839
|
+
...commonType ? { type: commonType } : {},
|
|
840
|
+
...Object.keys(commonProperties).length ? { properties: commonProperties } : {},
|
|
841
|
+
...commonRequired.length ? { required: commonRequired } : {}
|
|
842
|
+
}, root, path, warnings, seenRefs);
|
|
843
|
+
}
|
|
844
|
+
function commonObjectProperties(variants) {
|
|
845
|
+
const propertyMaps = variants.map((variant) => isObject(variant.properties) ? variant.properties : {});
|
|
846
|
+
if (propertyMaps.length === 0) return {};
|
|
847
|
+
const commonKeys = Object.keys(propertyMaps[0] ?? {}).filter((key) => propertyMaps.every((properties) => key in properties));
|
|
848
|
+
const output = {};
|
|
849
|
+
for (const key of commonKeys) output[key] = propertyMaps[0]?.[key];
|
|
850
|
+
return output;
|
|
851
|
+
}
|
|
852
|
+
function commonRequiredFields(variants) {
|
|
853
|
+
const requiredLists = variants.map((variant) => Array.isArray(variant.required) ? variant.required.filter((item) => typeof item === "string") : []);
|
|
854
|
+
if (requiredLists.length === 0) return [];
|
|
855
|
+
return requiredLists[0]?.filter((key) => requiredLists.every((list) => list.includes(key))) ?? [];
|
|
856
|
+
}
|
|
857
|
+
function commonScalar(values) {
|
|
858
|
+
const [first] = values;
|
|
859
|
+
return typeof first === "string" && values.every((value) => value === first) ? first : null;
|
|
860
|
+
}
|
|
861
|
+
function resolveLocalRef(root, ref) {
|
|
862
|
+
if (!ref.startsWith("#/")) return void 0;
|
|
863
|
+
const parts = ref.slice(2).split("/").map(unescapePointer);
|
|
864
|
+
let current = root;
|
|
865
|
+
for (const part of parts) {
|
|
866
|
+
if (!isObject(current) && !Array.isArray(current)) return void 0;
|
|
867
|
+
current = current[part];
|
|
868
|
+
}
|
|
869
|
+
return current;
|
|
870
|
+
}
|
|
871
|
+
function stripDefinitions(object) {
|
|
872
|
+
const { $defs: _defs, definitions: _definitions, ...rest } = object;
|
|
873
|
+
return rest;
|
|
874
|
+
}
|
|
875
|
+
function clone(value) {
|
|
876
|
+
return JSON.parse(JSON.stringify(value));
|
|
877
|
+
}
|
|
878
|
+
function isObject(value) {
|
|
879
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
880
|
+
}
|
|
881
|
+
function escapePointer(value) {
|
|
882
|
+
return value.replace(/~/g, "~0").replace(/\//g, "~1");
|
|
883
|
+
}
|
|
884
|
+
function unescapePointer(value) {
|
|
885
|
+
return value.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
// src/providers/retry.ts
|
|
889
|
+
var DEFAULT_MAX_RETRIES = 3;
|
|
890
|
+
var DEFAULT_MAX_WAIT_MS = 3e4;
|
|
891
|
+
var DEFAULT_INITIAL_WAIT_MS = 500;
|
|
892
|
+
var DEFAULT_PROVIDER_CONCURRENCY = 4;
|
|
893
|
+
var semaphores = /* @__PURE__ */ new Map();
|
|
894
|
+
function retryAfterMs(response, now = Date.now()) {
|
|
546
895
|
const raw = response.headers.get("retry-after");
|
|
547
896
|
if (!raw) return null;
|
|
548
897
|
const seconds = Number(raw);
|
|
549
|
-
if (Number.isFinite(seconds) && seconds >= 0) return Math.
|
|
898
|
+
if (Number.isFinite(seconds) && seconds >= 0) return Math.max(0, seconds * 1e3);
|
|
550
899
|
const dateMs = Date.parse(raw);
|
|
551
|
-
if (!Number.isNaN(dateMs)) return Math.
|
|
900
|
+
if (!Number.isNaN(dateMs)) return Math.max(dateMs - now, 0);
|
|
901
|
+
return null;
|
|
902
|
+
}
|
|
903
|
+
function backoffMs(attempt, options = {}) {
|
|
904
|
+
const initial = options.initialWaitMs ?? DEFAULT_INITIAL_WAIT_MS;
|
|
905
|
+
const max = options.maxWaitMs ?? DEFAULT_MAX_WAIT_MS;
|
|
906
|
+
return Math.min(initial * 2 ** Math.max(0, attempt - 1), max);
|
|
907
|
+
}
|
|
908
|
+
function retryWaitMs(response, attempt, options = {}) {
|
|
909
|
+
if (response.status === 429) {
|
|
910
|
+
const retryAfter = retryAfterMs(response);
|
|
911
|
+
if (retryAfter !== null) return Math.min(retryAfter, options.maxWaitMs ?? DEFAULT_MAX_WAIT_MS);
|
|
912
|
+
}
|
|
913
|
+
if (response.status >= 500 && response.status <= 599 || response.status === 429) {
|
|
914
|
+
return backoffMs(attempt, options);
|
|
915
|
+
}
|
|
552
916
|
return null;
|
|
553
917
|
}
|
|
554
|
-
function
|
|
555
|
-
const
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
918
|
+
async function fetchWithProviderRetry(options) {
|
|
919
|
+
const semaphore = getProviderSemaphore(options.provider, options.concurrency ?? DEFAULT_PROVIDER_CONCURRENCY);
|
|
920
|
+
return semaphore.run(() => fetchWithRetry(options));
|
|
921
|
+
}
|
|
922
|
+
async function fetchWithRetry(options) {
|
|
923
|
+
const maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
924
|
+
const sleep2 = options.sleep ?? defaultSleep;
|
|
925
|
+
let response;
|
|
926
|
+
for (let attempt = 0; attempt <= maxRetries; attempt += 1) {
|
|
927
|
+
response = await options.fetch();
|
|
928
|
+
const retryAttempt = attempt + 1;
|
|
929
|
+
const waitOptions = {
|
|
930
|
+
...options.initialWaitMs !== void 0 ? { initialWaitMs: options.initialWaitMs } : {},
|
|
931
|
+
...options.maxWaitMs !== void 0 ? { maxWaitMs: options.maxWaitMs } : {}
|
|
932
|
+
};
|
|
933
|
+
const waitMs = attempt < maxRetries ? retryWaitMs(response, retryAttempt, waitOptions) : null;
|
|
934
|
+
if (waitMs === null) return response;
|
|
935
|
+
await options.onThrottle?.({ provider: options.provider, attempt: retryAttempt, waitMs });
|
|
936
|
+
await sleep2(waitMs);
|
|
937
|
+
}
|
|
938
|
+
return response;
|
|
939
|
+
}
|
|
940
|
+
function getProviderSemaphore(provider, concurrency) {
|
|
941
|
+
const normalizedProvider = provider || "unknown";
|
|
942
|
+
const existing = semaphores.get(normalizedProvider);
|
|
943
|
+
if (existing && existing.limit === concurrency) return existing;
|
|
944
|
+
const created = new ProviderSemaphore(Math.max(1, Math.floor(concurrency)));
|
|
945
|
+
semaphores.set(normalizedProvider, created);
|
|
946
|
+
return created;
|
|
947
|
+
}
|
|
948
|
+
function defaultSleep(ms) {
|
|
949
|
+
return new Promise((resolve13) => setTimeout(resolve13, ms));
|
|
950
|
+
}
|
|
951
|
+
var ProviderSemaphore = class {
|
|
952
|
+
limit;
|
|
953
|
+
active = 0;
|
|
954
|
+
queue = [];
|
|
955
|
+
constructor(limit) {
|
|
956
|
+
this.limit = limit;
|
|
957
|
+
}
|
|
958
|
+
async run(fn) {
|
|
959
|
+
await this.acquire();
|
|
960
|
+
try {
|
|
961
|
+
return await fn();
|
|
962
|
+
} finally {
|
|
963
|
+
this.release();
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
acquire() {
|
|
967
|
+
if (this.active < this.limit) {
|
|
968
|
+
this.active += 1;
|
|
969
|
+
return Promise.resolve();
|
|
970
|
+
}
|
|
971
|
+
return new Promise((resolve13) => {
|
|
972
|
+
this.queue.push(() => {
|
|
973
|
+
this.active += 1;
|
|
974
|
+
resolve13();
|
|
975
|
+
});
|
|
976
|
+
});
|
|
977
|
+
}
|
|
978
|
+
release() {
|
|
979
|
+
this.active -= 1;
|
|
980
|
+
const next = this.queue.shift();
|
|
981
|
+
if (next) next();
|
|
982
|
+
}
|
|
983
|
+
};
|
|
984
|
+
|
|
985
|
+
// src/providers/openAiCompatible.ts
|
|
986
|
+
function providerConcurrency() {
|
|
987
|
+
const raw = Number(envValue({}, "TANYA_PROVIDER_CONCURRENCY"));
|
|
988
|
+
return Number.isFinite(raw) && raw > 0 ? Math.floor(raw) : 4;
|
|
560
989
|
}
|
|
561
990
|
var OpenAiCompatibleProvider = class {
|
|
562
991
|
id;
|
|
@@ -566,18 +995,20 @@ var OpenAiCompatibleProvider = class {
|
|
|
566
995
|
timeoutMs;
|
|
567
996
|
temperature;
|
|
568
997
|
topP;
|
|
998
|
+
adapter;
|
|
569
999
|
constructor(options) {
|
|
1000
|
+
this.adapter = resolveProviderAdapter({ provider: options.id, baseUrl: options.baseUrl });
|
|
570
1001
|
this.id = options.id;
|
|
571
1002
|
this.apiKey = options.apiKey;
|
|
572
|
-
this.baseUrl = options.baseUrl.replace(/\/$/, "");
|
|
573
|
-
this.model = options.model;
|
|
1003
|
+
this.baseUrl = (options.baseUrl || this.adapter.defaultBaseUrl || "").replace(/\/$/, "");
|
|
1004
|
+
this.model = options.model || this.adapter.defaultModel || "";
|
|
574
1005
|
const envTimeout = parseInt(envValue({}, "TANYA_TIMEOUT_MS"), 10);
|
|
575
1006
|
const envTimeoutMs = Number.isFinite(envTimeout) && envTimeout > 0 ? envTimeout : null;
|
|
576
1007
|
this.timeoutMs = envTimeoutMs ?? options.timeoutMs ?? 9e4;
|
|
577
1008
|
this.temperature = options.temperature ?? 0;
|
|
578
1009
|
this.topP = options.topP ?? 0.2;
|
|
579
1010
|
}
|
|
580
|
-
async *streamChat(
|
|
1011
|
+
async *streamChat(input) {
|
|
581
1012
|
if (!this.apiKey) {
|
|
582
1013
|
throw new Error(`Missing API key for provider "${this.id}".`);
|
|
583
1014
|
}
|
|
@@ -587,54 +1018,62 @@ var OpenAiCompatibleProvider = class {
|
|
|
587
1018
|
if (timeout) clearTimeout(timeout);
|
|
588
1019
|
timeout = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
589
1020
|
};
|
|
590
|
-
const
|
|
1021
|
+
const request = {
|
|
591
1022
|
model: this.model,
|
|
592
|
-
messages:
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
top_p: input2.topP ?? this.topP,
|
|
597
|
-
max_tokens: input2.maxTokens ?? 8192,
|
|
1023
|
+
messages: input.messages,
|
|
1024
|
+
temperature: input.temperature ?? this.temperature,
|
|
1025
|
+
top_p: input.topP ?? this.topP,
|
|
1026
|
+
max_tokens: input.maxTokens ?? 8192,
|
|
598
1027
|
stream: true
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
Authorization: `Bearer ${this.apiKey}`,
|
|
609
|
-
"Content-Type": "application/json"
|
|
610
|
-
},
|
|
611
|
-
body: requestBody
|
|
612
|
-
}).catch((error) => {
|
|
613
|
-
if (controller.signal.aborted) {
|
|
614
|
-
throw new Error(`Provider ${this.id} timed out before streaming a response.`);
|
|
615
|
-
}
|
|
616
|
-
throw error;
|
|
617
|
-
});
|
|
618
|
-
if (response.ok && response.body) break;
|
|
619
|
-
if (!RETRYABLE_HTTP_STATUSES.has(response.status) || attempt === MAX_FETCH_ATTEMPTS - 1) {
|
|
620
|
-
break;
|
|
1028
|
+
};
|
|
1029
|
+
const schemaWarnings = [];
|
|
1030
|
+
if (input.tools?.length) {
|
|
1031
|
+
if (this.adapter.capabilities.flattenSchemas) {
|
|
1032
|
+
const flattened = flattenToolDefinitions(input.tools);
|
|
1033
|
+
request.tools = flattened.schema;
|
|
1034
|
+
schemaWarnings.push(...flattened.warnings);
|
|
1035
|
+
} else {
|
|
1036
|
+
request.tools = input.tools;
|
|
621
1037
|
}
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
1038
|
+
request.tool_choice = "auto";
|
|
1039
|
+
}
|
|
1040
|
+
const requestBody = JSON.stringify(this.adapter.preRequest ? this.adapter.preRequest(request) : request);
|
|
1041
|
+
resetTimeout();
|
|
1042
|
+
const retryOptions = {
|
|
1043
|
+
provider: this.adapter.id,
|
|
1044
|
+
concurrency: providerConcurrency(),
|
|
1045
|
+
fetch: () => {
|
|
1046
|
+
resetTimeout();
|
|
1047
|
+
return fetch(`${this.baseUrl}/chat/completions`, {
|
|
1048
|
+
method: "POST",
|
|
1049
|
+
signal: controller.signal,
|
|
1050
|
+
headers: {
|
|
1051
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
1052
|
+
"Content-Type": "application/json"
|
|
1053
|
+
},
|
|
1054
|
+
body: requestBody
|
|
1055
|
+
}).catch((error) => {
|
|
1056
|
+
if (controller.signal.aborted) {
|
|
1057
|
+
throw new Error(`Provider ${this.id} timed out before streaming a response.`);
|
|
1058
|
+
}
|
|
1059
|
+
throw error;
|
|
1060
|
+
});
|
|
1061
|
+
},
|
|
1062
|
+
...input.onProviderThrottle ? { onThrottle: input.onProviderThrottle } : {}
|
|
1063
|
+
};
|
|
1064
|
+
const response = await fetchWithProviderRetry(retryOptions);
|
|
629
1065
|
if (!response.ok || !response.body) {
|
|
630
1066
|
if (timeout) clearTimeout(timeout);
|
|
631
|
-
const detail =
|
|
1067
|
+
const detail = await response.text().catch(() => "");
|
|
632
1068
|
throw new Error(`Provider ${this.id} returned HTTP ${response.status}: ${detail.slice(0, 500)}`);
|
|
633
1069
|
}
|
|
634
1070
|
const decoder = new TextDecoder();
|
|
635
1071
|
let buffer = "";
|
|
636
1072
|
const toolCallParts = /* @__PURE__ */ new Map();
|
|
637
1073
|
try {
|
|
1074
|
+
if (schemaWarnings.length > 0) {
|
|
1075
|
+
yield { schemaWarnings };
|
|
1076
|
+
}
|
|
638
1077
|
for await (const chunk of response.body) {
|
|
639
1078
|
resetTimeout();
|
|
640
1079
|
buffer += decoder.decode(chunk, { stream: true });
|
|
@@ -647,7 +1086,8 @@ var OpenAiCompatibleProvider = class {
|
|
|
647
1086
|
if (data === "[DONE]") return;
|
|
648
1087
|
let parsed;
|
|
649
1088
|
try {
|
|
650
|
-
|
|
1089
|
+
const rawParsed = JSON.parse(data);
|
|
1090
|
+
parsed = this.adapter.postResponse ? this.adapter.postResponse(rawParsed) : rawParsed;
|
|
651
1091
|
} catch (error) {
|
|
652
1092
|
if (envValue({}, "TANYA_DEBUG")) {
|
|
653
1093
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -700,7 +1140,7 @@ var OpenAiCompatibleProvider = class {
|
|
|
700
1140
|
// src/providers/factory.ts
|
|
701
1141
|
function createProvider(config) {
|
|
702
1142
|
return new OpenAiCompatibleProvider({
|
|
703
|
-
id: config.profile === "reasoner" ? "deepseek-reasoner" : config.provider,
|
|
1143
|
+
id: config.provider === "deepseek" && config.profile === "reasoner" ? "deepseek-reasoner" : config.provider,
|
|
704
1144
|
apiKey: config.apiKey,
|
|
705
1145
|
baseUrl: config.baseUrl,
|
|
706
1146
|
model: config.model,
|
|
@@ -710,6 +1150,161 @@ function createProvider(config) {
|
|
|
710
1150
|
});
|
|
711
1151
|
}
|
|
712
1152
|
|
|
1153
|
+
// src/providers/parser.ts
|
|
1154
|
+
var TOOL_CALL_CORRECTION_LIMIT = 3;
|
|
1155
|
+
function malformedToolCallCorrectionMessage(reason) {
|
|
1156
|
+
return `[your last tool call was malformed: ${reason}. Try again with valid JSON.]`;
|
|
1157
|
+
}
|
|
1158
|
+
function parseProviderToolCalls(rawToolCalls, options = {}) {
|
|
1159
|
+
const warnings = [];
|
|
1160
|
+
const failures = [];
|
|
1161
|
+
const toolCalls = [];
|
|
1162
|
+
rawToolCalls.forEach((raw, index) => {
|
|
1163
|
+
const parsed = parseOneToolCall(raw, index, options.turn ?? 0);
|
|
1164
|
+
warnings.push(...parsed.warnings);
|
|
1165
|
+
if (parsed.failure) {
|
|
1166
|
+
failures.push(parsed.failure);
|
|
1167
|
+
return;
|
|
1168
|
+
}
|
|
1169
|
+
toolCalls.push(parsed.toolCall);
|
|
1170
|
+
});
|
|
1171
|
+
return { toolCalls, warnings, failures };
|
|
1172
|
+
}
|
|
1173
|
+
function parseToolArguments(rawArguments) {
|
|
1174
|
+
if (rawArguments === void 0 || rawArguments === null || rawArguments === "") {
|
|
1175
|
+
return { ok: true, input: {} };
|
|
1176
|
+
}
|
|
1177
|
+
if (typeof rawArguments === "object") {
|
|
1178
|
+
return { ok: true, input: rawArguments };
|
|
1179
|
+
}
|
|
1180
|
+
if (typeof rawArguments !== "string") {
|
|
1181
|
+
return {
|
|
1182
|
+
ok: false,
|
|
1183
|
+
reason: `tool arguments must be JSON object or string, got ${typeof rawArguments}`,
|
|
1184
|
+
rawArguments: String(rawArguments)
|
|
1185
|
+
};
|
|
1186
|
+
}
|
|
1187
|
+
if (!rawArguments.trim()) return { ok: true, input: {} };
|
|
1188
|
+
try {
|
|
1189
|
+
return { ok: true, input: JSON.parse(rawArguments) };
|
|
1190
|
+
} catch {
|
|
1191
|
+
const preview = previewRawToolArguments(rawArguments);
|
|
1192
|
+
return {
|
|
1193
|
+
ok: false,
|
|
1194
|
+
reason: `malformed JSON arguments: ${preview}`,
|
|
1195
|
+
rawArguments: preview
|
|
1196
|
+
};
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
function parseOneToolCall(raw, index, turn) {
|
|
1200
|
+
const warnings = [];
|
|
1201
|
+
if (!isRecord2(raw)) {
|
|
1202
|
+
const toolCall3 = fallbackToolCall(turn, index, "__malformed_tool_call__", "");
|
|
1203
|
+
return {
|
|
1204
|
+
toolCall: toolCall3,
|
|
1205
|
+
warnings,
|
|
1206
|
+
failure: { reason: `tool call must be an object, got ${typeof raw}`, toolCall: toolCall3, raw }
|
|
1207
|
+
};
|
|
1208
|
+
}
|
|
1209
|
+
const idValue = typeof raw.id === "string" && raw.id.trim() ? raw.id : void 0;
|
|
1210
|
+
const id = idValue ?? `call_${turn}_${index}`;
|
|
1211
|
+
if (!idValue) {
|
|
1212
|
+
warnings.push({
|
|
1213
|
+
reason: `missing tool call id; synthesized ${id}`,
|
|
1214
|
+
toolCallId: id,
|
|
1215
|
+
raw
|
|
1216
|
+
});
|
|
1217
|
+
}
|
|
1218
|
+
const functionRecord = isRecord2(raw.function) ? raw.function : raw;
|
|
1219
|
+
if (!isRecord2(raw.function)) {
|
|
1220
|
+
warnings.push({
|
|
1221
|
+
reason: "missing function wrapper; accepted top-level name/arguments",
|
|
1222
|
+
toolCallId: id,
|
|
1223
|
+
raw
|
|
1224
|
+
});
|
|
1225
|
+
}
|
|
1226
|
+
const name = typeof functionRecord.name === "string" ? functionRecord.name.trim() : "";
|
|
1227
|
+
if (!name) {
|
|
1228
|
+
const toolCall3 = fallbackToolCall(turn, index, "__malformed_tool_call__", stringifyArguments(functionRecord.arguments));
|
|
1229
|
+
return {
|
|
1230
|
+
toolCall: toolCall3,
|
|
1231
|
+
warnings,
|
|
1232
|
+
failure: { reason: "missing function name", toolCall: toolCall3, raw }
|
|
1233
|
+
};
|
|
1234
|
+
}
|
|
1235
|
+
const normalizedArguments = normalizeArguments(functionRecord.arguments, warnings, id, name, raw);
|
|
1236
|
+
const toolCall2 = {
|
|
1237
|
+
id,
|
|
1238
|
+
type: "function",
|
|
1239
|
+
function: {
|
|
1240
|
+
name,
|
|
1241
|
+
arguments: normalizedArguments
|
|
1242
|
+
}
|
|
1243
|
+
};
|
|
1244
|
+
const parsedArguments = parseToolArguments(normalizedArguments);
|
|
1245
|
+
if (!parsedArguments.ok) {
|
|
1246
|
+
return {
|
|
1247
|
+
toolCall: toolCall2,
|
|
1248
|
+
warnings,
|
|
1249
|
+
failure: { reason: parsedArguments.reason, toolCall: toolCall2, raw }
|
|
1250
|
+
};
|
|
1251
|
+
}
|
|
1252
|
+
return { toolCall: toolCall2, warnings };
|
|
1253
|
+
}
|
|
1254
|
+
function normalizeArguments(value, warnings, toolCallId, tool, raw) {
|
|
1255
|
+
if (value === void 0 || value === null) {
|
|
1256
|
+
warnings.push({
|
|
1257
|
+
reason: "missing arguments; using empty object",
|
|
1258
|
+
toolCallId,
|
|
1259
|
+
tool,
|
|
1260
|
+
raw
|
|
1261
|
+
});
|
|
1262
|
+
return "{}";
|
|
1263
|
+
}
|
|
1264
|
+
if (typeof value === "string") return value;
|
|
1265
|
+
if (typeof value === "object") {
|
|
1266
|
+
warnings.push({
|
|
1267
|
+
reason: "arguments arrived as object; stringified for OpenAI-compatible history",
|
|
1268
|
+
toolCallId,
|
|
1269
|
+
tool,
|
|
1270
|
+
raw
|
|
1271
|
+
});
|
|
1272
|
+
return JSON.stringify(value);
|
|
1273
|
+
}
|
|
1274
|
+
warnings.push({
|
|
1275
|
+
reason: `arguments arrived as ${typeof value}; stringified for correction`,
|
|
1276
|
+
toolCallId,
|
|
1277
|
+
tool,
|
|
1278
|
+
raw
|
|
1279
|
+
});
|
|
1280
|
+
return String(value);
|
|
1281
|
+
}
|
|
1282
|
+
function stringifyArguments(value) {
|
|
1283
|
+
if (value === void 0 || value === null) return "";
|
|
1284
|
+
if (typeof value === "string") return value;
|
|
1285
|
+
try {
|
|
1286
|
+
return JSON.stringify(value);
|
|
1287
|
+
} catch {
|
|
1288
|
+
return String(value);
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
function fallbackToolCall(turn, index, name, args) {
|
|
1292
|
+
return {
|
|
1293
|
+
id: `call_${turn}_${index}`,
|
|
1294
|
+
type: "function",
|
|
1295
|
+
function: {
|
|
1296
|
+
name,
|
|
1297
|
+
arguments: args
|
|
1298
|
+
}
|
|
1299
|
+
};
|
|
1300
|
+
}
|
|
1301
|
+
function previewRawToolArguments(raw) {
|
|
1302
|
+
return raw.length > 500 ? `${raw.slice(0, 500)}...[truncated ${raw.length - 500} chars]` : raw;
|
|
1303
|
+
}
|
|
1304
|
+
function isRecord2(value) {
|
|
1305
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
1306
|
+
}
|
|
1307
|
+
|
|
713
1308
|
// src/safety/workspace.ts
|
|
714
1309
|
import { existsSync as existsSync3, lstatSync, realpathSync } from "fs";
|
|
715
1310
|
import { dirname as dirname2, resolve as resolve3, relative as relative2 } from "path";
|
|
@@ -757,28 +1352,28 @@ import sharp2 from "sharp";
|
|
|
757
1352
|
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
758
1353
|
import { dirname as dirname3 } from "path";
|
|
759
1354
|
import sharp from "sharp";
|
|
760
|
-
function asRecord(
|
|
761
|
-
return
|
|
1355
|
+
function asRecord(input) {
|
|
1356
|
+
return input && typeof input === "object" ? input : {};
|
|
762
1357
|
}
|
|
763
|
-
function asString2(
|
|
764
|
-
const value = asRecord(
|
|
1358
|
+
function asString2(input, key) {
|
|
1359
|
+
const value = asRecord(input)[key];
|
|
765
1360
|
if (typeof value !== "string" || !value.trim()) throw new Error(`Missing string field: ${key}`);
|
|
766
1361
|
return value;
|
|
767
1362
|
}
|
|
768
|
-
function asOptionalString(
|
|
769
|
-
const value = asRecord(
|
|
1363
|
+
function asOptionalString(input, key) {
|
|
1364
|
+
const value = asRecord(input)[key];
|
|
770
1365
|
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
771
1366
|
}
|
|
772
|
-
function asOptionalNumber(
|
|
773
|
-
const value = asRecord(
|
|
1367
|
+
function asOptionalNumber(input, key, fallback) {
|
|
1368
|
+
const value = asRecord(input)[key];
|
|
774
1369
|
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
775
1370
|
}
|
|
776
1371
|
function ensureRelativePath(path) {
|
|
777
1372
|
if (path.startsWith("/")) throw new Error(`Path must be relative to the workspace: ${path}`);
|
|
778
1373
|
return path;
|
|
779
1374
|
}
|
|
780
|
-
function optionalStringArray(
|
|
781
|
-
const value = asRecord(
|
|
1375
|
+
function optionalStringArray(input, key, fallback) {
|
|
1376
|
+
const value = asRecord(input)[key];
|
|
782
1377
|
if (!Array.isArray(value)) return fallback;
|
|
783
1378
|
return value.filter((item) => typeof item === "string" && item.trim().length > 0);
|
|
784
1379
|
}
|
|
@@ -855,12 +1450,12 @@ var resizeImageTool = {
|
|
|
855
1450
|
}
|
|
856
1451
|
}
|
|
857
1452
|
},
|
|
858
|
-
async run(
|
|
859
|
-
const source = ensureRelativePath(asString2(
|
|
860
|
-
const destination = ensureRelativePath(asString2(
|
|
861
|
-
const width = Math.round(asOptionalNumber(
|
|
862
|
-
const height = Math.round(asOptionalNumber(
|
|
863
|
-
const background = asOptionalString(
|
|
1453
|
+
async run(input, context) {
|
|
1454
|
+
const source = ensureRelativePath(asString2(input, "source"));
|
|
1455
|
+
const destination = ensureRelativePath(asString2(input, "destination"));
|
|
1456
|
+
const width = Math.round(asOptionalNumber(input, "width", 0));
|
|
1457
|
+
const height = Math.round(asOptionalNumber(input, "height", width));
|
|
1458
|
+
const background = asOptionalString(input, "background") ?? "#ffffff";
|
|
864
1459
|
if (width <= 0 || height <= 0) return { ok: false, summary: "Invalid image dimensions.", error: "width and height must be positive." };
|
|
865
1460
|
const sourceAbs = resolveInsideWorkspace(context.workspace, source);
|
|
866
1461
|
const destinationAbs = resolveInsideWorkspace(context.workspace, destination);
|
|
@@ -895,12 +1490,12 @@ var renderSvgToPngTool = {
|
|
|
895
1490
|
}
|
|
896
1491
|
}
|
|
897
1492
|
},
|
|
898
|
-
async run(
|
|
899
|
-
const source = ensureRelativePath(asString2(
|
|
900
|
-
const destination = ensureRelativePath(asString2(
|
|
901
|
-
const width = Math.round(asOptionalNumber(
|
|
902
|
-
const height = Math.round(asOptionalNumber(
|
|
903
|
-
const background = asOptionalString(
|
|
1493
|
+
async run(input, context) {
|
|
1494
|
+
const source = ensureRelativePath(asString2(input, "source"));
|
|
1495
|
+
const destination = ensureRelativePath(asString2(input, "destination"));
|
|
1496
|
+
const width = Math.round(asOptionalNumber(input, "width", 1024));
|
|
1497
|
+
const height = Math.round(asOptionalNumber(input, "height", width));
|
|
1498
|
+
const background = asOptionalString(input, "background") ?? "#ffffff";
|
|
904
1499
|
if (width <= 0 || height <= 0) return { ok: false, summary: "Invalid image dimensions.", error: "width and height must be positive." };
|
|
905
1500
|
const sourceAbs = resolveInsideWorkspace(context.workspace, source);
|
|
906
1501
|
const destinationAbs = resolveInsideWorkspace(context.workspace, destination);
|
|
@@ -940,12 +1535,12 @@ var createAppleAppIconSetTool = {
|
|
|
940
1535
|
}
|
|
941
1536
|
}
|
|
942
1537
|
},
|
|
943
|
-
async run(
|
|
944
|
-
const source = ensureRelativePath(asString2(
|
|
945
|
-
const outputDir = ensureRelativePath(asString2(
|
|
946
|
-
const platforms = optionalStringArray(
|
|
1538
|
+
async run(input, context) {
|
|
1539
|
+
const source = ensureRelativePath(asString2(input, "source"));
|
|
1540
|
+
const outputDir = ensureRelativePath(asString2(input, "outputDir"));
|
|
1541
|
+
const platforms = optionalStringArray(input, "platforms", ["ios"]);
|
|
947
1542
|
const slots = slotsForPlatforms(platforms);
|
|
948
|
-
const background = asOptionalString(
|
|
1543
|
+
const background = asOptionalString(input, "background") ?? "#ffffff";
|
|
949
1544
|
if (slots.length === 0) {
|
|
950
1545
|
return { ok: false, summary: "No Apple icon platforms selected.", error: "Use platforms ['ios'], ['macos'], or both." };
|
|
951
1546
|
}
|
|
@@ -1016,12 +1611,12 @@ var createAndroidLauncherIconSetTool = {
|
|
|
1016
1611
|
}
|
|
1017
1612
|
}
|
|
1018
1613
|
},
|
|
1019
|
-
async run(
|
|
1020
|
-
const source = ensureRelativePath(asString2(
|
|
1021
|
-
const resDir = ensureRelativePath(asString2(
|
|
1022
|
-
const background = asOptionalString(
|
|
1023
|
-
const iconName = sanitizeName(asOptionalString(
|
|
1024
|
-
const roundIconName = sanitizeName(asOptionalString(
|
|
1614
|
+
async run(input, context) {
|
|
1615
|
+
const source = ensureRelativePath(asString2(input, "source"));
|
|
1616
|
+
const resDir = ensureRelativePath(asString2(input, "resDir"));
|
|
1617
|
+
const background = asOptionalString(input, "background") ?? "#ffffff";
|
|
1618
|
+
const iconName = sanitizeName(asOptionalString(input, "iconName") ?? "ic_launcher");
|
|
1619
|
+
const roundIconName = sanitizeName(asOptionalString(input, "roundIconName") ?? "ic_launcher_round");
|
|
1025
1620
|
const sourceAbs = resolveInsideWorkspace(context.workspace, source);
|
|
1026
1621
|
const generatedFiles = [];
|
|
1027
1622
|
for (const density of androidLauncherDensities) {
|
|
@@ -1120,10 +1715,10 @@ var validateAppleAppIconSetTool = {
|
|
|
1120
1715
|
}
|
|
1121
1716
|
}
|
|
1122
1717
|
},
|
|
1123
|
-
async run(
|
|
1124
|
-
const appIconSetDir = ensureRelativePath(asString2(
|
|
1125
|
-
const platforms = optionalStringArray(
|
|
1126
|
-
const requireNoAlpha = asRecord(
|
|
1718
|
+
async run(input, context) {
|
|
1719
|
+
const appIconSetDir = ensureRelativePath(asString2(input, "appIconSetDir"));
|
|
1720
|
+
const platforms = optionalStringArray(input, "platforms", ["ios"]);
|
|
1721
|
+
const requireNoAlpha = asRecord(input).requireNoAlpha !== false;
|
|
1127
1722
|
const expectedSlots = slotsForPlatforms(platforms);
|
|
1128
1723
|
const contentsPath = `${appIconSetDir}/Contents.json`;
|
|
1129
1724
|
const contentsText = await readFile(resolveInsideWorkspace(context.workspace, contentsPath), "utf8");
|
|
@@ -1173,10 +1768,10 @@ var validateAndroidLauncherIconSetTool = {
|
|
|
1173
1768
|
}
|
|
1174
1769
|
}
|
|
1175
1770
|
},
|
|
1176
|
-
async run(
|
|
1177
|
-
const resDir = ensureRelativePath(asString2(
|
|
1178
|
-
const iconName = sanitizeName(asOptionalString(
|
|
1179
|
-
const roundIconName = sanitizeName(asOptionalString(
|
|
1771
|
+
async run(input, context) {
|
|
1772
|
+
const resDir = ensureRelativePath(asString2(input, "resDir"));
|
|
1773
|
+
const iconName = sanitizeName(asOptionalString(input, "iconName") ?? "ic_launcher");
|
|
1774
|
+
const roundIconName = sanitizeName(asOptionalString(input, "roundIconName") ?? "ic_launcher_round");
|
|
1180
1775
|
const problems = [];
|
|
1181
1776
|
for (const density of androidLauncherDensities) {
|
|
1182
1777
|
for (const [fileName, expectedPx] of [
|
|
@@ -1227,19 +1822,19 @@ import { mkdir as mkdir2, writeFile as writeFile2, copyFile } from "fs/promises"
|
|
|
1227
1822
|
import { dirname as dirname4, join as join2 } from "path";
|
|
1228
1823
|
import { spawn, spawnSync } from "child_process";
|
|
1229
1824
|
import WebSocket from "ws";
|
|
1230
|
-
function asRecord2(
|
|
1231
|
-
return
|
|
1825
|
+
function asRecord2(input) {
|
|
1826
|
+
return input && typeof input === "object" ? input : {};
|
|
1232
1827
|
}
|
|
1233
|
-
function asOptionalString2(
|
|
1234
|
-
const value = asRecord2(
|
|
1828
|
+
function asOptionalString2(input, key) {
|
|
1829
|
+
const value = asRecord2(input)[key];
|
|
1235
1830
|
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
1236
1831
|
}
|
|
1237
|
-
function asOptionalNumber2(
|
|
1238
|
-
const value = asRecord2(
|
|
1832
|
+
function asOptionalNumber2(input, key, fallback) {
|
|
1833
|
+
const value = asRecord2(input)[key];
|
|
1239
1834
|
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
1240
1835
|
}
|
|
1241
|
-
function optionalStringArray2(
|
|
1242
|
-
const value = asRecord2(
|
|
1836
|
+
function optionalStringArray2(input, key, fallback) {
|
|
1837
|
+
const value = asRecord2(input)[key];
|
|
1243
1838
|
if (!Array.isArray(value)) return fallback;
|
|
1244
1839
|
return value.filter((item) => typeof item === "string" && item.trim().length > 0);
|
|
1245
1840
|
}
|
|
@@ -1406,7 +2001,7 @@ function terminalSvg(config, frame) {
|
|
|
1406
2001
|
${body}
|
|
1407
2002
|
</svg>`;
|
|
1408
2003
|
}
|
|
1409
|
-
function
|
|
2004
|
+
function sleep(ms) {
|
|
1410
2005
|
return new Promise((resolve13) => setTimeout(resolve13, ms));
|
|
1411
2006
|
}
|
|
1412
2007
|
async function waitForJson(url, timeout = 1e4) {
|
|
@@ -1417,7 +2012,7 @@ async function waitForJson(url, timeout = 1e4) {
|
|
|
1417
2012
|
if (res.ok) return await res.json();
|
|
1418
2013
|
} catch {
|
|
1419
2014
|
}
|
|
1420
|
-
await
|
|
2015
|
+
await sleep(120);
|
|
1421
2016
|
}
|
|
1422
2017
|
throw new Error(`Timed out waiting for ${url}`);
|
|
1423
2018
|
}
|
|
@@ -1483,7 +2078,7 @@ async function captureFrames(config, frameDir, svgDir, chromeProfile) {
|
|
|
1483
2078
|
await page.send("Emulation.setDefaultBackgroundColorOverride", { color: { r: 0, g: 0, b: 0, a: 0 } });
|
|
1484
2079
|
const html = `<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;width:${config.width}px;height:${config.height}px;overflow:hidden;background:transparent;}svg{display:block;width:${config.width}px;height:${config.height}px;}</style></head><body></body></html>`;
|
|
1485
2080
|
await page.send("Page.navigate", { url: `data:text/html;charset=utf-8,${encodeURIComponent(html)}` });
|
|
1486
|
-
await
|
|
2081
|
+
await sleep(250);
|
|
1487
2082
|
const totalFrames = Math.round(config.fps * config.duration);
|
|
1488
2083
|
for (let i = 0; i < totalFrames; i += 1) {
|
|
1489
2084
|
const n = String(i + 1).padStart(4, "0");
|
|
@@ -1647,25 +2242,25 @@ var generateVideoAssetTool = {
|
|
|
1647
2242
|
}
|
|
1648
2243
|
}
|
|
1649
2244
|
},
|
|
1650
|
-
async run(
|
|
1651
|
-
const preset = asOptionalString2(
|
|
1652
|
-
const outputDir = asOptionalString2(
|
|
1653
|
-
const
|
|
2245
|
+
async run(input, context) {
|
|
2246
|
+
const preset = asOptionalString2(input, "preset");
|
|
2247
|
+
const outputDir = asOptionalString2(input, "outputDir");
|
|
2248
|
+
const basename5 = asOptionalString2(input, "basename");
|
|
1654
2249
|
const options = {
|
|
1655
|
-
width: asOptionalNumber2(
|
|
1656
|
-
height: asOptionalNumber2(
|
|
1657
|
-
fps: asOptionalNumber2(
|
|
1658
|
-
duration: asOptionalNumber2(
|
|
1659
|
-
formats: optionalStringArray2(
|
|
1660
|
-
lines: optionalStringArray2(
|
|
2250
|
+
width: asOptionalNumber2(input, "width", 980),
|
|
2251
|
+
height: asOptionalNumber2(input, "height", 1012),
|
|
2252
|
+
fps: asOptionalNumber2(input, "fps", 30),
|
|
2253
|
+
duration: asOptionalNumber2(input, "duration", 3),
|
|
2254
|
+
formats: optionalStringArray2(input, "formats", ["webm", "mov", "poster"]),
|
|
2255
|
+
lines: optionalStringArray2(input, "lines", [])
|
|
1661
2256
|
};
|
|
1662
2257
|
if (preset) options.preset = preset;
|
|
1663
2258
|
if (outputDir) options.outputDir = outputDir;
|
|
1664
|
-
if (
|
|
1665
|
-
const title = asOptionalString2(
|
|
1666
|
-
const tab = asOptionalString2(
|
|
1667
|
-
const secondaryTab = asOptionalString2(
|
|
1668
|
-
const badge = asOptionalString2(
|
|
2259
|
+
if (basename5) options.basename = basename5;
|
|
2260
|
+
const title = asOptionalString2(input, "title");
|
|
2261
|
+
const tab = asOptionalString2(input, "tab");
|
|
2262
|
+
const secondaryTab = asOptionalString2(input, "secondaryTab");
|
|
2263
|
+
const badge = asOptionalString2(input, "badge");
|
|
1669
2264
|
if (title) options.title = title;
|
|
1670
2265
|
if (tab) options.tab = tab;
|
|
1671
2266
|
if (secondaryTab) options.secondaryTab = secondaryTab;
|
|
@@ -1773,24 +2368,24 @@ var artifactFileExtensions = /* @__PURE__ */ new Set([
|
|
|
1773
2368
|
".yaml",
|
|
1774
2369
|
".css"
|
|
1775
2370
|
]);
|
|
1776
|
-
function asRecord3(
|
|
1777
|
-
return
|
|
2371
|
+
function asRecord3(input) {
|
|
2372
|
+
return input && typeof input === "object" ? input : {};
|
|
1778
2373
|
}
|
|
1779
|
-
function asString3(
|
|
1780
|
-
const value = asRecord3(
|
|
2374
|
+
function asString3(input, key) {
|
|
2375
|
+
const value = asRecord3(input)[key];
|
|
1781
2376
|
if (typeof value !== "string" || !value.trim()) throw new Error(`Missing string field: ${key}`);
|
|
1782
2377
|
return value.trim();
|
|
1783
2378
|
}
|
|
1784
|
-
function asOptionalString3(
|
|
1785
|
-
const value = asRecord3(
|
|
2379
|
+
function asOptionalString3(input, key) {
|
|
2380
|
+
const value = asRecord3(input)[key];
|
|
1786
2381
|
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
1787
2382
|
}
|
|
1788
|
-
function asOptionalNumber3(
|
|
1789
|
-
const value = asRecord3(
|
|
2383
|
+
function asOptionalNumber3(input, key, fallback) {
|
|
2384
|
+
const value = asRecord3(input)[key];
|
|
1790
2385
|
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
1791
2386
|
}
|
|
1792
|
-
function asOptionalBoolean(
|
|
1793
|
-
const value = asRecord3(
|
|
2387
|
+
function asOptionalBoolean(input, key, fallback) {
|
|
2388
|
+
const value = asRecord3(input)[key];
|
|
1794
2389
|
if (typeof value === "boolean") return value;
|
|
1795
2390
|
if (typeof value === "string") return /^(true|yes|1)$/i.test(value.trim());
|
|
1796
2391
|
return fallback;
|
|
@@ -2075,38 +2670,38 @@ function inferTaskSignals(task, workspace) {
|
|
|
2075
2670
|
return { platforms: [...platforms].sort(), domains: [...domains].sort() };
|
|
2076
2671
|
}
|
|
2077
2672
|
function recommendedVerificationCommands(files, packageScripts, signals) {
|
|
2078
|
-
const
|
|
2673
|
+
const commands2 = /* @__PURE__ */ new Set();
|
|
2079
2674
|
const hasExplicitPlatformSignal = signals.platforms.length > 0;
|
|
2080
2675
|
const hasScript = (name) => Object.prototype.hasOwnProperty.call(packageScripts, name);
|
|
2081
|
-
if (hasScript("prisma:generate"))
|
|
2082
|
-
if (hasScript("typecheck"))
|
|
2083
|
-
if (hasScript("test"))
|
|
2084
|
-
if (hasScript("lint"))
|
|
2085
|
-
if (hasScript("build"))
|
|
2676
|
+
if (hasScript("prisma:generate")) commands2.add("npm run prisma:generate");
|
|
2677
|
+
if (hasScript("typecheck")) commands2.add("npm run typecheck");
|
|
2678
|
+
if (hasScript("test")) commands2.add("npm test");
|
|
2679
|
+
if (hasScript("lint")) commands2.add("npm run lint");
|
|
2680
|
+
if (hasScript("build")) commands2.add("npm run build");
|
|
2086
2681
|
const text2 = files.join("\n").toLowerCase();
|
|
2087
2682
|
if (signals.platforms.includes("android") || !hasExplicitPlatformSignal && /gradlew|settings\.gradle|androidmanifest\.xml/.test(text2)) {
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
if (/ktlint/.test(text2))
|
|
2683
|
+
commands2.add("./gradlew test --no-daemon");
|
|
2684
|
+
commands2.add("./gradlew assembleDebug --no-daemon");
|
|
2685
|
+
if (/ktlint/.test(text2)) commands2.add("./gradlew ktlintCheck --no-daemon");
|
|
2091
2686
|
}
|
|
2092
2687
|
if (signals.platforms.includes("ios") || signals.platforms.includes("macos") || !hasExplicitPlatformSignal && /\.xcodeproj|\.xcworkspace/.test(text2)) {
|
|
2093
|
-
|
|
2688
|
+
commands2.add("xcodebuild -list");
|
|
2094
2689
|
}
|
|
2095
2690
|
if (signals.platforms.includes("script")) {
|
|
2096
2691
|
if (/cargo\.toml/.test(text2)) {
|
|
2097
|
-
|
|
2098
|
-
|
|
2692
|
+
commands2.add("cargo build --release");
|
|
2693
|
+
commands2.add("cargo test");
|
|
2099
2694
|
}
|
|
2100
2695
|
if (/pyproject\.toml|setup\.py/.test(text2)) {
|
|
2101
|
-
|
|
2102
|
-
|
|
2696
|
+
commands2.add("python -m pytest");
|
|
2697
|
+
commands2.add("python -m build");
|
|
2103
2698
|
}
|
|
2104
2699
|
if (/go\.mod/.test(text2)) {
|
|
2105
|
-
|
|
2106
|
-
|
|
2700
|
+
commands2.add("go build ./...");
|
|
2701
|
+
commands2.add("go test ./...");
|
|
2107
2702
|
}
|
|
2108
2703
|
}
|
|
2109
|
-
return [...
|
|
2704
|
+
return [...commands2];
|
|
2110
2705
|
}
|
|
2111
2706
|
function capabilityPacksForSignals(signals) {
|
|
2112
2707
|
const packs = [];
|
|
@@ -2215,16 +2810,16 @@ async function findArtifactsForSignals(params) {
|
|
|
2215
2810
|
}
|
|
2216
2811
|
return merged;
|
|
2217
2812
|
}
|
|
2218
|
-
async function buildTaskBrief(
|
|
2219
|
-
const maxArtifacts = Math.min(
|
|
2220
|
-
const maxContextFiles = Math.min(
|
|
2221
|
-
const files = await collectFiles(
|
|
2222
|
-
const packageScripts = await readPackageScripts(
|
|
2223
|
-
const signals = inferTaskSignals(
|
|
2813
|
+
async function buildTaskBrief(input) {
|
|
2814
|
+
const maxArtifacts = Math.min(input.maxArtifacts ?? 12, 40);
|
|
2815
|
+
const maxContextFiles = Math.min(input.maxContextFiles ?? 16, 50);
|
|
2816
|
+
const files = await collectFiles(input.workspace, 1e3);
|
|
2817
|
+
const packageScripts = await readPackageScripts(input.workspace);
|
|
2818
|
+
const signals = inferTaskSignals(input.task, input.workspace);
|
|
2224
2819
|
const contextFiles = files.filter((file) => !file.endsWith("/")).map((file) => ({ path: file, role: classifyContextFile(file) })).filter((file) => Boolean(file.role)).slice(0, maxContextFiles);
|
|
2225
|
-
const artifactQuery = [
|
|
2820
|
+
const artifactQuery = [input.task, signals.platforms.join(" "), signals.domains.join(" ")].filter(Boolean).join(" ");
|
|
2226
2821
|
const artifacts = await findArtifactsForSignals({
|
|
2227
|
-
workspace:
|
|
2822
|
+
workspace: input.workspace,
|
|
2228
2823
|
query: artifactQuery,
|
|
2229
2824
|
signals,
|
|
2230
2825
|
maxResults: maxArtifacts
|
|
@@ -2243,7 +2838,7 @@ async function buildTaskBrief(input2) {
|
|
|
2243
2838
|
"scan_secrets"
|
|
2244
2839
|
].filter((value) => typeof value === "string");
|
|
2245
2840
|
return {
|
|
2246
|
-
task:
|
|
2841
|
+
task: input.task,
|
|
2247
2842
|
signals,
|
|
2248
2843
|
contextFiles,
|
|
2249
2844
|
artifacts,
|
|
@@ -2277,13 +2872,13 @@ var inspectProjectContextTool = {
|
|
|
2277
2872
|
}
|
|
2278
2873
|
}
|
|
2279
2874
|
},
|
|
2280
|
-
async run(
|
|
2281
|
-
const record = asRecord3(
|
|
2875
|
+
async run(input, context) {
|
|
2876
|
+
const record = asRecord3(input);
|
|
2282
2877
|
const root = resolveOptionalWorkspacePath(context, asOptionalString3(record, "path"));
|
|
2283
2878
|
const rootRel = normalizePath2(relative3(context.workspace, root)) || ".";
|
|
2284
|
-
const includeExcerpts = asOptionalBoolean(
|
|
2285
|
-
const maxFiles = Math.min(asOptionalNumber3(
|
|
2286
|
-
const maxExcerptChars = Math.min(asOptionalNumber3(
|
|
2879
|
+
const includeExcerpts = asOptionalBoolean(input, "includeExcerpts", true);
|
|
2880
|
+
const maxFiles = Math.min(asOptionalNumber3(input, "maxFiles", 500), 2e3);
|
|
2881
|
+
const maxExcerptChars = Math.min(asOptionalNumber3(input, "maxExcerptChars", 900), 4e3);
|
|
2287
2882
|
const files = await collectFiles(root, maxFiles);
|
|
2288
2883
|
const platforms = detectPlatforms(files);
|
|
2289
2884
|
const packageScripts = await readPackageScripts(root);
|
|
@@ -2351,11 +2946,11 @@ var findReusableArtifactsTool = {
|
|
|
2351
2946
|
}
|
|
2352
2947
|
}
|
|
2353
2948
|
},
|
|
2354
|
-
async run(
|
|
2355
|
-
const query = asOptionalString3(
|
|
2356
|
-
const platform = asOptionalString3(
|
|
2357
|
-
const maxResults = Math.min(asOptionalNumber3(
|
|
2358
|
-
const artifactRoot = asOptionalString3(
|
|
2949
|
+
async run(input, context) {
|
|
2950
|
+
const query = asOptionalString3(input, "query") ?? "";
|
|
2951
|
+
const platform = asOptionalString3(input, "platform");
|
|
2952
|
+
const maxResults = Math.min(asOptionalNumber3(input, "maxResults", 12), 50);
|
|
2953
|
+
const artifactRoot = asOptionalString3(input, "artifactRoot");
|
|
2359
2954
|
const artifacts = await findArtifacts({
|
|
2360
2955
|
workspace: context.workspace,
|
|
2361
2956
|
query,
|
|
@@ -2395,13 +2990,13 @@ var buildTaskBriefTool = {
|
|
|
2395
2990
|
}
|
|
2396
2991
|
}
|
|
2397
2992
|
},
|
|
2398
|
-
async run(
|
|
2399
|
-
const task = asString3(
|
|
2993
|
+
async run(input, context) {
|
|
2994
|
+
const task = asString3(input, "task");
|
|
2400
2995
|
const brief = await buildTaskBrief({
|
|
2401
2996
|
workspace: context.workspace,
|
|
2402
2997
|
task,
|
|
2403
|
-
maxArtifacts: Math.min(asOptionalNumber3(
|
|
2404
|
-
maxContextFiles: Math.min(asOptionalNumber3(
|
|
2998
|
+
maxArtifacts: Math.min(asOptionalNumber3(input, "maxArtifacts", 12), 40),
|
|
2999
|
+
maxContextFiles: Math.min(asOptionalNumber3(input, "maxContextFiles", 16), 50)
|
|
2405
3000
|
});
|
|
2406
3001
|
return {
|
|
2407
3002
|
ok: true,
|
|
@@ -2513,13 +3108,13 @@ function scoreNote(relPath, title, markdown, queryTerms) {
|
|
|
2513
3108
|
function safeMaterializedPath(relPath) {
|
|
2514
3109
|
return normalizePath3(relPath).replace(/^\.+\/?/, "").replace(/[^A-Za-z0-9._/-]/g, "_").replace(/\/+/g, "/");
|
|
2515
3110
|
}
|
|
2516
|
-
async function searchObsidianNotes(
|
|
2517
|
-
const vault = resolve5(
|
|
3111
|
+
async function searchObsidianNotes(input) {
|
|
3112
|
+
const vault = resolve5(input.vaultPath);
|
|
2518
3113
|
if (!existsSync6(vault)) return [];
|
|
2519
|
-
const queryTerms = terms(
|
|
2520
|
-
const maxResults = Math.min(
|
|
2521
|
-
const maxFiles = Math.min(
|
|
2522
|
-
const maxExcerptChars = Math.min(
|
|
3114
|
+
const queryTerms = terms(input.query);
|
|
3115
|
+
const maxResults = Math.min(input.maxResults ?? 5, 20);
|
|
3116
|
+
const maxFiles = Math.min(input.maxFiles ?? 1e3, 5e3);
|
|
3117
|
+
const maxExcerptChars = Math.min(input.maxExcerptChars ?? 1800, 8e3);
|
|
2523
3118
|
const files = await collectMarkdownFiles(vault, maxFiles);
|
|
2524
3119
|
const results = [];
|
|
2525
3120
|
for (const file of files) {
|
|
@@ -2545,13 +3140,13 @@ async function searchObsidianNotes(input2) {
|
|
|
2545
3140
|
}
|
|
2546
3141
|
return results.sort((a, b) => b.score - a.score || String(b.modifiedAt ?? "").localeCompare(String(a.modifiedAt ?? "")) || a.path.localeCompare(b.path)).slice(0, maxResults);
|
|
2547
3142
|
}
|
|
2548
|
-
async function materializeObsidianContext(
|
|
3143
|
+
async function materializeObsidianContext(input) {
|
|
2549
3144
|
const notes = await searchObsidianNotes({
|
|
2550
|
-
vaultPath:
|
|
2551
|
-
query:
|
|
2552
|
-
maxResults:
|
|
3145
|
+
vaultPath: input.vaultPath,
|
|
3146
|
+
query: input.query,
|
|
3147
|
+
maxResults: input.maxResults ?? 5
|
|
2553
3148
|
});
|
|
2554
|
-
const contextRoot = resolve5(
|
|
3149
|
+
const contextRoot = resolve5(input.workspace, ".tania", "context", "obsidian");
|
|
2555
3150
|
const contextFiles = [];
|
|
2556
3151
|
for (const note of notes) {
|
|
2557
3152
|
const targetRel = safeMaterializedPath(note.path);
|
|
@@ -2584,7 +3179,7 @@ async function materializeObsidianContext(input2) {
|
|
|
2584
3179
|
await mkdir3(contextRoot, { recursive: true });
|
|
2585
3180
|
await writeFile3(
|
|
2586
3181
|
resolve5(contextRoot, "manifest.json"),
|
|
2587
|
-
JSON.stringify({ generatedAt: (/* @__PURE__ */ new Date()).toISOString(), query:
|
|
3182
|
+
JSON.stringify({ generatedAt: (/* @__PURE__ */ new Date()).toISOString(), query: input.query, notes }, null, 2),
|
|
2588
3183
|
"utf8"
|
|
2589
3184
|
);
|
|
2590
3185
|
}
|
|
@@ -2592,19 +3187,19 @@ async function materializeObsidianContext(input2) {
|
|
|
2592
3187
|
}
|
|
2593
3188
|
|
|
2594
3189
|
// src/tools/obsidianTools.ts
|
|
2595
|
-
function asRecord4(
|
|
2596
|
-
return
|
|
3190
|
+
function asRecord4(input) {
|
|
3191
|
+
return input && typeof input === "object" ? input : {};
|
|
2597
3192
|
}
|
|
2598
|
-
function asOptionalString4(
|
|
2599
|
-
const value = asRecord4(
|
|
3193
|
+
function asOptionalString4(input, key) {
|
|
3194
|
+
const value = asRecord4(input)[key];
|
|
2600
3195
|
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
2601
3196
|
}
|
|
2602
|
-
function asOptionalNumber4(
|
|
2603
|
-
const value = asRecord4(
|
|
3197
|
+
function asOptionalNumber4(input, key, fallback) {
|
|
3198
|
+
const value = asRecord4(input)[key];
|
|
2604
3199
|
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
2605
3200
|
}
|
|
2606
|
-
function asOptionalBoolean2(
|
|
2607
|
-
const value = asRecord4(
|
|
3201
|
+
function asOptionalBoolean2(input, key, fallback) {
|
|
3202
|
+
const value = asRecord4(input)[key];
|
|
2608
3203
|
if (typeof value === "boolean") return value;
|
|
2609
3204
|
if (typeof value === "string") return /^(true|yes|1)$/i.test(value.trim());
|
|
2610
3205
|
return fallback;
|
|
@@ -2629,8 +3224,8 @@ var searchObsidianNotesTool = {
|
|
|
2629
3224
|
}
|
|
2630
3225
|
}
|
|
2631
3226
|
},
|
|
2632
|
-
async run(
|
|
2633
|
-
const query = asOptionalString4(
|
|
3227
|
+
async run(input, context) {
|
|
3228
|
+
const query = asOptionalString4(input, "query");
|
|
2634
3229
|
if (!query) throw new Error("Missing string field: query");
|
|
2635
3230
|
const vaultPath = envValue({}, "TANYA_OBSIDIAN_VAULT").trim();
|
|
2636
3231
|
if (!vaultPath) {
|
|
@@ -2640,8 +3235,8 @@ var searchObsidianNotesTool = {
|
|
|
2640
3235
|
output: { notes: [], guidance: "Set TANYA_OBSIDIAN_VAULT to enable generic note retrieval." }
|
|
2641
3236
|
};
|
|
2642
3237
|
}
|
|
2643
|
-
const maxResults = Math.min(asOptionalNumber4(
|
|
2644
|
-
if (asOptionalBoolean2(
|
|
3238
|
+
const maxResults = Math.min(asOptionalNumber4(input, "maxResults", 5), 20);
|
|
3239
|
+
if (asOptionalBoolean2(input, "materialize", false)) {
|
|
2645
3240
|
const materialized = await materializeObsidianContext({
|
|
2646
3241
|
workspace: context.workspace,
|
|
2647
3242
|
vaultPath,
|
|
@@ -2679,24 +3274,24 @@ function localPropertiesWriteError() {
|
|
|
2679
3274
|
error: "local.properties is machine-local Android SDK configuration. Do not create or modify it; use ANDROID_HOME or ANDROID_SDK_ROOT for verification instead."
|
|
2680
3275
|
};
|
|
2681
3276
|
}
|
|
2682
|
-
function asRecord5(
|
|
2683
|
-
return
|
|
3277
|
+
function asRecord5(input) {
|
|
3278
|
+
return input && typeof input === "object" ? input : {};
|
|
2684
3279
|
}
|
|
2685
|
-
function asString4(
|
|
2686
|
-
const value = asRecord5(
|
|
3280
|
+
function asString4(input, key) {
|
|
3281
|
+
const value = asRecord5(input)[key];
|
|
2687
3282
|
if (typeof value !== "string" || !value.trim()) throw new Error(`Missing string field: ${key}`);
|
|
2688
3283
|
return value;
|
|
2689
3284
|
}
|
|
2690
|
-
function asOptionalNumber5(
|
|
2691
|
-
const value = asRecord5(
|
|
3285
|
+
function asOptionalNumber5(input, key, fallback) {
|
|
3286
|
+
const value = asRecord5(input)[key];
|
|
2692
3287
|
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
2693
3288
|
}
|
|
2694
|
-
function asOptionalString5(
|
|
2695
|
-
const value = asRecord5(
|
|
3289
|
+
function asOptionalString5(input, key) {
|
|
3290
|
+
const value = asRecord5(input)[key];
|
|
2696
3291
|
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
2697
3292
|
}
|
|
2698
|
-
function asOptionalBoolean3(
|
|
2699
|
-
const value = asRecord5(
|
|
3293
|
+
function asOptionalBoolean3(input, key, fallback) {
|
|
3294
|
+
const value = asRecord5(input)[key];
|
|
2700
3295
|
if (typeof value === "boolean") return value;
|
|
2701
3296
|
if (typeof value === "string") return /^(true|yes|1)$/i.test(value.trim());
|
|
2702
3297
|
return fallback;
|
|
@@ -2738,15 +3333,15 @@ function runProcess(command, args, context, timeoutMs, cwd = context.workspace)
|
|
|
2738
3333
|
});
|
|
2739
3334
|
child.on("close", (code) => {
|
|
2740
3335
|
clearTimeout(timer);
|
|
2741
|
-
const
|
|
3336
|
+
const output = `${stdout}${stderr ? `
|
|
2742
3337
|
${stderr}` : ""}`.trim();
|
|
2743
|
-
const displayOutput = summarizeProcessOutput(
|
|
3338
|
+
const displayOutput = summarizeProcessOutput(output, 12e3);
|
|
2744
3339
|
const baseResult = {
|
|
2745
3340
|
ok: code === 0,
|
|
2746
|
-
summary: buildProcessSummary("Command", code,
|
|
3341
|
+
summary: buildProcessSummary("Command", code, output, displayOutput.truncated),
|
|
2747
3342
|
output: displayOutput.text
|
|
2748
3343
|
};
|
|
2749
|
-
resolve13(code === 0 ? baseResult : { ...baseResult, error:
|
|
3344
|
+
resolve13(code === 0 ? baseResult : { ...baseResult, error: output.slice(0, 2e3) });
|
|
2750
3345
|
});
|
|
2751
3346
|
child.on("error", (err) => {
|
|
2752
3347
|
clearTimeout(timer);
|
|
@@ -2851,9 +3446,9 @@ ${stderr}` : ""}`.trim();
|
|
|
2851
3446
|
});
|
|
2852
3447
|
child.on("close", (code) => {
|
|
2853
3448
|
cleanupShell();
|
|
2854
|
-
const
|
|
3449
|
+
const output = outputSoFar();
|
|
2855
3450
|
if (cancelled || context.signal?.aborted) {
|
|
2856
|
-
const partialOutput = summarizeProcessOutput(
|
|
3451
|
+
const partialOutput = summarizeProcessOutput(output, 16e3).text;
|
|
2857
3452
|
resolve13({
|
|
2858
3453
|
ok: false,
|
|
2859
3454
|
summary: "Shell cancelled by user. Partial output captured.",
|
|
@@ -2864,13 +3459,13 @@ ${stderr}` : ""}`.trim();
|
|
|
2864
3459
|
});
|
|
2865
3460
|
return;
|
|
2866
3461
|
}
|
|
2867
|
-
const displayOutput = summarizeProcessOutput(
|
|
3462
|
+
const displayOutput = summarizeProcessOutput(output, 16e3);
|
|
2868
3463
|
const baseResult = {
|
|
2869
3464
|
ok: code === 0,
|
|
2870
|
-
summary: buildProcessSummary("Shell", code,
|
|
3465
|
+
summary: buildProcessSummary("Shell", code, output, displayOutput.truncated),
|
|
2871
3466
|
output: displayOutput.text
|
|
2872
3467
|
};
|
|
2873
|
-
resolve13(code === 0 ? baseResult : { ...baseResult, error:
|
|
3468
|
+
resolve13(code === 0 ? baseResult : { ...baseResult, error: output.slice(0, 2e3) });
|
|
2874
3469
|
});
|
|
2875
3470
|
child.on("error", (err) => {
|
|
2876
3471
|
cleanupShell();
|
|
@@ -2900,28 +3495,28 @@ function unsafeHostPackageMutation(script) {
|
|
|
2900
3495
|
}
|
|
2901
3496
|
return null;
|
|
2902
3497
|
}
|
|
2903
|
-
function summarizeProcessOutput(
|
|
2904
|
-
if (
|
|
3498
|
+
function summarizeProcessOutput(output, maxChars) {
|
|
3499
|
+
if (output.length <= maxChars) return { text: output, truncated: false };
|
|
2905
3500
|
const headSize = Math.floor(maxChars * 0.35);
|
|
2906
3501
|
const tailSize = maxChars - headSize - 120;
|
|
2907
3502
|
return {
|
|
2908
|
-
text: `${
|
|
3503
|
+
text: `${output.slice(0, headSize)}
|
|
2909
3504
|
|
|
2910
3505
|
[output truncated: showing head and tail; exit code remains authoritative]
|
|
2911
3506
|
|
|
2912
|
-
${
|
|
3507
|
+
${output.slice(-tailSize)}`,
|
|
2913
3508
|
truncated: true
|
|
2914
3509
|
};
|
|
2915
3510
|
}
|
|
2916
|
-
function buildProcessSummary(kind, code,
|
|
3511
|
+
function buildProcessSummary(kind, code, output, truncated) {
|
|
2917
3512
|
const exit = code ?? "unknown";
|
|
2918
|
-
const successHint = code === 0 && /BUILD SUCCEEDED|Test Suite '.+' passed|0 failures|Process completed successfully/i.test(
|
|
3513
|
+
const successHint = code === 0 && /BUILD SUCCEEDED|Test Suite '.+' passed|0 failures|Process completed successfully/i.test(output) ? " Success marker found." : "";
|
|
2919
3514
|
const truncatedHint = truncated ? " Output was truncated for display, but the exit code is authoritative." : "";
|
|
2920
3515
|
return `${kind} exited ${exit}.${successHint}${truncatedHint}`;
|
|
2921
3516
|
}
|
|
2922
|
-
function filterTypeScriptErrorOutput(
|
|
2923
|
-
if (typeof
|
|
2924
|
-
const lines =
|
|
3517
|
+
function filterTypeScriptErrorOutput(output) {
|
|
3518
|
+
if (typeof output !== "string" || !output.trim()) return null;
|
|
3519
|
+
const lines = output.split(/\r?\n/);
|
|
2925
3520
|
const kept = [];
|
|
2926
3521
|
const errorPattern = /^[^:]+\.tsx?:\d+:\d+\s+-\s+error\s+TS\d+:/;
|
|
2927
3522
|
for (let i = 0; i < lines.length; i += 1) {
|
|
@@ -2961,7 +3556,7 @@ function resolveToolCwd(context, cwdInput) {
|
|
|
2961
3556
|
if (!cwdInput) return context.workspace;
|
|
2962
3557
|
return isAbsolute2(cwdInput) ? resolveInsideWorkspace(context.workspace, cwdInput) : resolveInsideWorkspace(context.workspace, ensureRelativePath3(cwdInput));
|
|
2963
3558
|
}
|
|
2964
|
-
function runProcessWithInput(command, args,
|
|
3559
|
+
function runProcessWithInput(command, args, input, context, timeoutMs) {
|
|
2965
3560
|
return new Promise((resolve13) => {
|
|
2966
3561
|
const child = spawn2(command, args, {
|
|
2967
3562
|
cwd: context.workspace,
|
|
@@ -2981,20 +3576,20 @@ function runProcessWithInput(command, args, input2, context, timeoutMs) {
|
|
|
2981
3576
|
});
|
|
2982
3577
|
child.on("close", (code) => {
|
|
2983
3578
|
clearTimeout(timer);
|
|
2984
|
-
const
|
|
3579
|
+
const output = `${stdout}${stderr ? `
|
|
2985
3580
|
${stderr}` : ""}`.trim();
|
|
2986
3581
|
const baseResult = {
|
|
2987
3582
|
ok: code === 0,
|
|
2988
3583
|
summary: `Command exited ${code ?? "unknown"}.`,
|
|
2989
|
-
output:
|
|
3584
|
+
output: output.slice(0, 12e3)
|
|
2990
3585
|
};
|
|
2991
|
-
resolve13(code === 0 ? baseResult : { ...baseResult, error:
|
|
3586
|
+
resolve13(code === 0 ? baseResult : { ...baseResult, error: output.slice(0, 2e3) });
|
|
2992
3587
|
});
|
|
2993
3588
|
child.on("error", (err) => {
|
|
2994
3589
|
clearTimeout(timer);
|
|
2995
3590
|
resolve13({ ok: false, summary: "Command failed to start.", error: err.message });
|
|
2996
3591
|
});
|
|
2997
|
-
child.stdin.end(
|
|
3592
|
+
child.stdin.end(input);
|
|
2998
3593
|
});
|
|
2999
3594
|
}
|
|
3000
3595
|
function stripPatchPath(path, stripLevel) {
|
|
@@ -3051,9 +3646,9 @@ var listFilesTool = {
|
|
|
3051
3646
|
}
|
|
3052
3647
|
}
|
|
3053
3648
|
},
|
|
3054
|
-
async run(
|
|
3055
|
-
const maxFiles = Math.min(asOptionalNumber5(
|
|
3056
|
-
const path = asOptionalString5(
|
|
3649
|
+
async run(input, context) {
|
|
3650
|
+
const maxFiles = Math.min(asOptionalNumber5(input, "maxFiles", 120), 500);
|
|
3651
|
+
const path = asOptionalString5(input, "path");
|
|
3057
3652
|
const root = path ? resolveInsideWorkspace(context.workspace, ensureRelativePath3(path)) : context.workspace;
|
|
3058
3653
|
const files = collectFiles2(root, maxFiles).map((file) => path ? `${path.replace(/\/+$/, "")}/${file}` : file);
|
|
3059
3654
|
return { ok: true, summary: `Listed ${files.length} file${files.length === 1 ? "" : "s"}.`, output: files };
|
|
@@ -3078,9 +3673,9 @@ var readFileTool = {
|
|
|
3078
3673
|
}
|
|
3079
3674
|
}
|
|
3080
3675
|
},
|
|
3081
|
-
async run(
|
|
3082
|
-
const path = asString4(
|
|
3083
|
-
const maxChars = Math.min(asOptionalNumber5(
|
|
3676
|
+
async run(input, context) {
|
|
3677
|
+
const path = asString4(input, "path");
|
|
3678
|
+
const maxChars = Math.min(asOptionalNumber5(input, "maxChars", 12e3), 4e4);
|
|
3084
3679
|
const abs = resolveInsideWorkspace(context.workspace, path);
|
|
3085
3680
|
const content = await readFile4(abs, "utf8");
|
|
3086
3681
|
return {
|
|
@@ -3110,9 +3705,9 @@ var writeFileTool = {
|
|
|
3110
3705
|
}
|
|
3111
3706
|
}
|
|
3112
3707
|
},
|
|
3113
|
-
async run(
|
|
3114
|
-
const path = asString4(
|
|
3115
|
-
const content = asString4(
|
|
3708
|
+
async run(input, context) {
|
|
3709
|
+
const path = asString4(input, "path");
|
|
3710
|
+
const content = asString4(input, "content");
|
|
3116
3711
|
if (isProtectedLocalConfigPath(path)) return localPropertiesWriteError();
|
|
3117
3712
|
const abs = resolveInsideWorkspace(context.workspace, path);
|
|
3118
3713
|
await mkdir4(dirname6(abs), { recursive: true });
|
|
@@ -3147,9 +3742,9 @@ var searchTool = {
|
|
|
3147
3742
|
}
|
|
3148
3743
|
}
|
|
3149
3744
|
},
|
|
3150
|
-
async run(
|
|
3151
|
-
const query = asString4(
|
|
3152
|
-
const maxResults = Math.min(asOptionalNumber5(
|
|
3745
|
+
async run(input, context) {
|
|
3746
|
+
const query = asString4(input, "query");
|
|
3747
|
+
const maxResults = Math.min(asOptionalNumber5(input, "maxResults", 80), 300);
|
|
3153
3748
|
const result = await runProcess("rg", ["-n", "--hidden", "-g", "!node_modules", "-g", "!.git", query], context, 2e4);
|
|
3154
3749
|
if (!result.ok && typeof result.output === "string" && !result.output) {
|
|
3155
3750
|
return { ok: true, summary: "No matches.", output: [] };
|
|
@@ -3179,13 +3774,13 @@ var runCommandTool = {
|
|
|
3179
3774
|
}
|
|
3180
3775
|
}
|
|
3181
3776
|
},
|
|
3182
|
-
async run(
|
|
3183
|
-
const command = asString4(
|
|
3184
|
-
const rawArgs = asRecord5(
|
|
3777
|
+
async run(input, context) {
|
|
3778
|
+
const command = asString4(input, "command");
|
|
3779
|
+
const rawArgs = asRecord5(input).args;
|
|
3185
3780
|
const args = Array.isArray(rawArgs) ? rawArgs.filter((arg) => typeof arg === "string") : [];
|
|
3186
|
-
const cwdInput = asOptionalString5(
|
|
3781
|
+
const cwdInput = asOptionalString5(input, "cwd");
|
|
3187
3782
|
const cwd = resolveToolCwd(context, cwdInput);
|
|
3188
|
-
const timeoutMs = Math.min(asOptionalNumber5(
|
|
3783
|
+
const timeoutMs = Math.min(asOptionalNumber5(input, "timeoutMs", 12e4), 3e5);
|
|
3189
3784
|
const result = await runProcess(command, args, context, timeoutMs, cwd);
|
|
3190
3785
|
return maybeFilterTypeScriptErrorResult(result, commandRunsTypeScript(command, args));
|
|
3191
3786
|
}
|
|
@@ -3211,8 +3806,8 @@ var runShellTool = {
|
|
|
3211
3806
|
}
|
|
3212
3807
|
}
|
|
3213
3808
|
},
|
|
3214
|
-
async run(
|
|
3215
|
-
const script = asOptionalString5(
|
|
3809
|
+
async run(input, context) {
|
|
3810
|
+
const script = asOptionalString5(input, "script") ?? asString4(input, "command");
|
|
3216
3811
|
if (/\b(rm\s+-rf|sudo|ssh|scp|curl\s+[^|>]*\|\s*(?:sh|bash|zsh)|while\s+true|tail\s+-f)\b/.test(script)) {
|
|
3217
3812
|
return { ok: false, summary: "Shell script rejected by safety checks.", error: "Use bounded, non-destructive, non-interactive commands only." };
|
|
3218
3813
|
}
|
|
@@ -3230,9 +3825,9 @@ var runShellTool = {
|
|
|
3230
3825
|
if (/(?:>\s*["']?[^&|;\n]*local\.properties\b|tee\s+[^|;\n]*local\.properties\b)/.test(script)) {
|
|
3231
3826
|
return localPropertiesWriteError();
|
|
3232
3827
|
}
|
|
3233
|
-
const cwdInput = asOptionalString5(
|
|
3828
|
+
const cwdInput = asOptionalString5(input, "cwd");
|
|
3234
3829
|
const cwd = resolveToolCwd(context, cwdInput);
|
|
3235
|
-
const timeoutMs = Math.min(asOptionalNumber5(
|
|
3830
|
+
const timeoutMs = Math.min(asOptionalNumber5(input, "timeoutMs", 12e4), 3e5);
|
|
3236
3831
|
const result = await runShell(script, context, timeoutMs, cwd);
|
|
3237
3832
|
return maybeFilterTypeScriptErrorResult(result, shellRunsTypeScript(script));
|
|
3238
3833
|
}
|
|
@@ -3256,9 +3851,9 @@ var applyPatchTool = {
|
|
|
3256
3851
|
}
|
|
3257
3852
|
}
|
|
3258
3853
|
},
|
|
3259
|
-
async run(
|
|
3260
|
-
const patch = asString4(
|
|
3261
|
-
const explicitStrip = asOptionalNumber5(
|
|
3854
|
+
async run(input, context) {
|
|
3855
|
+
const patch = asString4(input, "patch");
|
|
3856
|
+
const explicitStrip = asOptionalNumber5(input, "stripLevel", Number.NaN);
|
|
3262
3857
|
const stripLevel = Number.isFinite(explicitStrip) ? explicitStrip : inferPatchStripLevel(patch);
|
|
3263
3858
|
const files = extractPatchFiles(patch, stripLevel);
|
|
3264
3859
|
if (files.length === 0) {
|
|
@@ -3280,7 +3875,7 @@ var applyPatchTool = {
|
|
|
3280
3875
|
context,
|
|
3281
3876
|
6e4
|
|
3282
3877
|
);
|
|
3283
|
-
const
|
|
3878
|
+
const output = typeof result.output === "string" ? result.output : "";
|
|
3284
3879
|
if (!result.ok) {
|
|
3285
3880
|
return {
|
|
3286
3881
|
...result,
|
|
@@ -3293,7 +3888,7 @@ var applyPatchTool = {
|
|
|
3293
3888
|
return {
|
|
3294
3889
|
ok: true,
|
|
3295
3890
|
summary: `Applied patch to ${files.length} file${files.length === 1 ? "" : "s"}.${backupNote}`,
|
|
3296
|
-
output
|
|
3891
|
+
output,
|
|
3297
3892
|
files
|
|
3298
3893
|
};
|
|
3299
3894
|
}
|
|
@@ -3319,12 +3914,12 @@ var searchReplaceTool = {
|
|
|
3319
3914
|
}
|
|
3320
3915
|
}
|
|
3321
3916
|
},
|
|
3322
|
-
async run(
|
|
3323
|
-
const path = asString4(
|
|
3324
|
-
const oldString = asString4(
|
|
3325
|
-
const newString = asRecord5(
|
|
3917
|
+
async run(input, context) {
|
|
3918
|
+
const path = asString4(input, "path");
|
|
3919
|
+
const oldString = asString4(input, "old_string");
|
|
3920
|
+
const newString = asRecord5(input).new_string;
|
|
3326
3921
|
if (typeof newString !== "string") throw new Error("Missing string field: new_string");
|
|
3327
|
-
const expectedCount = asOptionalNumber5(
|
|
3922
|
+
const expectedCount = asOptionalNumber5(input, "expected_count", 1);
|
|
3328
3923
|
if (isProtectedLocalConfigPath(path)) return localPropertiesWriteError();
|
|
3329
3924
|
let abs;
|
|
3330
3925
|
try {
|
|
@@ -3383,10 +3978,10 @@ var copyFileTool = {
|
|
|
3383
3978
|
}
|
|
3384
3979
|
}
|
|
3385
3980
|
},
|
|
3386
|
-
async run(
|
|
3387
|
-
const source = ensureRelativePath3(asString4(
|
|
3388
|
-
const destination = ensureRelativePath3(asString4(
|
|
3389
|
-
const overwrite = asRecord5(
|
|
3981
|
+
async run(input, context) {
|
|
3982
|
+
const source = ensureRelativePath3(asString4(input, "source"));
|
|
3983
|
+
const destination = ensureRelativePath3(asString4(input, "destination"));
|
|
3984
|
+
const overwrite = asRecord5(input).overwrite !== false;
|
|
3390
3985
|
const sourceAbs = resolveInsideWorkspace(context.workspace, source);
|
|
3391
3986
|
const destinationAbs = resolveInsideWorkspace(context.workspace, destination);
|
|
3392
3987
|
await mkdir4(dirname6(destinationAbs), { recursive: true });
|
|
@@ -3414,10 +4009,10 @@ var copyDirTool = {
|
|
|
3414
4009
|
}
|
|
3415
4010
|
}
|
|
3416
4011
|
},
|
|
3417
|
-
async run(
|
|
3418
|
-
const source = ensureRelativePath3(asString4(
|
|
3419
|
-
const destination = ensureRelativePath3(asString4(
|
|
3420
|
-
const overwrite = asRecord5(
|
|
4012
|
+
async run(input, context) {
|
|
4013
|
+
const source = ensureRelativePath3(asString4(input, "source"));
|
|
4014
|
+
const destination = ensureRelativePath3(asString4(input, "destination"));
|
|
4015
|
+
const overwrite = asRecord5(input).overwrite !== false;
|
|
3421
4016
|
const sourceAbs = resolveInsideWorkspace(context.workspace, source);
|
|
3422
4017
|
const destinationAbs = resolveInsideWorkspace(context.workspace, destination);
|
|
3423
4018
|
await cp(sourceAbs, destinationAbs, { recursive: true, force: overwrite, errorOnExist: !overwrite });
|
|
@@ -3448,9 +4043,9 @@ var validateApiContractRoutesTool = {
|
|
|
3448
4043
|
}
|
|
3449
4044
|
}
|
|
3450
4045
|
},
|
|
3451
|
-
async run(
|
|
3452
|
-
const source = ensureRelativePath3(asString4(
|
|
3453
|
-
const target = ensureRelativePath3(asString4(
|
|
4046
|
+
async run(input, context) {
|
|
4047
|
+
const source = ensureRelativePath3(asString4(input, "source"));
|
|
4048
|
+
const target = ensureRelativePath3(asString4(input, "target"));
|
|
3454
4049
|
const sourceText = await readFile4(resolveInsideWorkspace(context.workspace, source), "utf8");
|
|
3455
4050
|
const targetText = await readFile4(resolveInsideWorkspace(context.workspace, target), "utf8");
|
|
3456
4051
|
const sourceRoutes = parseMarkdownApiRoutes(sourceText);
|
|
@@ -3494,14 +4089,14 @@ var validateAndroidProjectConfigTool = {
|
|
|
3494
4089
|
}
|
|
3495
4090
|
}
|
|
3496
4091
|
},
|
|
3497
|
-
async run(
|
|
3498
|
-
const manifestPath = ensureRelativePath3(asString4(
|
|
3499
|
-
const gradlePath = ensureRelativePath3(asString4(
|
|
3500
|
-
const minCompileSdk = asOptionalNumber5(
|
|
3501
|
-
const minTargetSdk = asOptionalNumber5(
|
|
3502
|
-
const minSdk = asOptionalNumber5(
|
|
3503
|
-
const expectedIcon = asOptionalString5(
|
|
3504
|
-
const expectedRoundIcon = asOptionalString5(
|
|
4092
|
+
async run(input, context) {
|
|
4093
|
+
const manifestPath = ensureRelativePath3(asString4(input, "manifestPath"));
|
|
4094
|
+
const gradlePath = ensureRelativePath3(asString4(input, "gradlePath"));
|
|
4095
|
+
const minCompileSdk = asOptionalNumber5(input, "minCompileSdk", 35);
|
|
4096
|
+
const minTargetSdk = asOptionalNumber5(input, "minTargetSdk", 35);
|
|
4097
|
+
const minSdk = asOptionalNumber5(input, "minSdk", 26);
|
|
4098
|
+
const expectedIcon = asOptionalString5(input, "expectedIcon") ?? "@mipmap/ic_launcher";
|
|
4099
|
+
const expectedRoundIcon = asOptionalString5(input, "expectedRoundIcon") ?? "@mipmap/ic_launcher_round";
|
|
3505
4100
|
const manifest = await readFile4(resolveInsideWorkspace(context.workspace, manifestPath), "utf8");
|
|
3506
4101
|
const gradle = await readFile4(resolveInsideWorkspace(context.workspace, gradlePath), "utf8");
|
|
3507
4102
|
const problems = [];
|
|
@@ -3544,9 +4139,9 @@ var validateAppleProjectFilesTool = {
|
|
|
3544
4139
|
}
|
|
3545
4140
|
}
|
|
3546
4141
|
},
|
|
3547
|
-
async run(
|
|
3548
|
-
const record = asRecord5(
|
|
3549
|
-
const xcodeprojPath = asOptionalString5(
|
|
4142
|
+
async run(input, context) {
|
|
4143
|
+
const record = asRecord5(input);
|
|
4144
|
+
const xcodeprojPath = asOptionalString5(input, "xcodeprojPath");
|
|
3550
4145
|
const requiredPaths = Array.isArray(record.requiredPaths) ? record.requiredPaths.filter((value) => typeof value === "string" && value.trim().length > 0) : [];
|
|
3551
4146
|
const requireProjectReferences = record.requireProjectReferences === true;
|
|
3552
4147
|
const problems = [];
|
|
@@ -3566,8 +4161,8 @@ var validateAppleProjectFilesTool = {
|
|
|
3566
4161
|
problems.push(`Missing ${relPath}.`);
|
|
3567
4162
|
}
|
|
3568
4163
|
if (requireProjectReferences && pbxprojText) {
|
|
3569
|
-
const
|
|
3570
|
-
if (!pbxprojText.includes(
|
|
4164
|
+
const basename5 = relPath.split("/").filter(Boolean).pop() ?? relPath;
|
|
4165
|
+
if (!pbxprojText.includes(basename5)) problems.push(`project.pbxproj does not reference ${basename5}.`);
|
|
3571
4166
|
}
|
|
3572
4167
|
}
|
|
3573
4168
|
return {
|
|
@@ -3599,9 +4194,9 @@ var validateFastlaneConfigTool = {
|
|
|
3599
4194
|
}
|
|
3600
4195
|
}
|
|
3601
4196
|
},
|
|
3602
|
-
async run(
|
|
3603
|
-
const record = asRecord5(
|
|
3604
|
-
const fastfilePath = asOptionalString5(
|
|
4197
|
+
async run(input, context) {
|
|
4198
|
+
const record = asRecord5(input);
|
|
4199
|
+
const fastfilePath = asOptionalString5(input, "fastfilePath") ?? "fastlane/Fastfile";
|
|
3605
4200
|
const requiredLanes = Array.isArray(record.requiredLanes) ? record.requiredLanes.filter((value) => typeof value === "string" && value.trim().length > 0).map((value) => value.trim()) : [];
|
|
3606
4201
|
const requiredFiles = Array.isArray(record.requiredFiles) ? record.requiredFiles.filter((value) => typeof value === "string" && value.trim().length > 0).map((value) => value.trim()) : [];
|
|
3607
4202
|
const forbiddenFiles = Array.isArray(record.forbiddenFiles) ? record.forbiddenFiles.filter((value) => typeof value === "string" && value.trim().length > 0).map((value) => value.trim()) : [];
|
|
@@ -3681,9 +4276,9 @@ var validatePrismaSchemaTool = {
|
|
|
3681
4276
|
}
|
|
3682
4277
|
}
|
|
3683
4278
|
},
|
|
3684
|
-
async run(
|
|
3685
|
-
const record = asRecord5(
|
|
3686
|
-
const schemaPath = ensureRelativePath3(asOptionalString5(
|
|
4279
|
+
async run(input, context) {
|
|
4280
|
+
const record = asRecord5(input);
|
|
4281
|
+
const schemaPath = ensureRelativePath3(asOptionalString5(input, "schemaPath") ?? "prisma/schema.prisma");
|
|
3687
4282
|
const requiredModels = Array.isArray(record.requiredModels) ? record.requiredModels.filter((value) => typeof value === "string" && value.trim().length > 0) : [];
|
|
3688
4283
|
const forbiddenModels = Array.isArray(record.forbiddenModels) ? record.forbiddenModels.filter((value) => typeof value === "string" && value.trim().length > 0) : [];
|
|
3689
4284
|
const schema = await readFile4(resolveInsideWorkspace(context.workspace, schemaPath), "utf8");
|
|
@@ -3722,11 +4317,11 @@ var applyArtifactTool = {
|
|
|
3722
4317
|
}
|
|
3723
4318
|
}
|
|
3724
4319
|
},
|
|
3725
|
-
async run(
|
|
3726
|
-
const artifactPath = ensureRelativePath3(asString4(
|
|
3727
|
-
const targetPath = ensureRelativePath3(asString4(
|
|
4320
|
+
async run(input, context) {
|
|
4321
|
+
const artifactPath = ensureRelativePath3(asString4(input, "artifactPath"));
|
|
4322
|
+
const targetPath = ensureRelativePath3(asString4(input, "targetPath"));
|
|
3728
4323
|
if (isProtectedLocalConfigPath(targetPath)) return localPropertiesWriteError();
|
|
3729
|
-
const overwrite = asRecord5(
|
|
4324
|
+
const overwrite = asRecord5(input).overwrite !== false;
|
|
3730
4325
|
const sourceAbs = resolveInsideWorkspace(context.workspace, artifactPath);
|
|
3731
4326
|
const targetAbs = resolveInsideWorkspace(context.workspace, targetPath);
|
|
3732
4327
|
await mkdir4(dirname6(targetAbs), { recursive: true });
|
|
@@ -3802,13 +4397,13 @@ var createIosSplashTool = {
|
|
|
3802
4397
|
}
|
|
3803
4398
|
}
|
|
3804
4399
|
},
|
|
3805
|
-
async run(
|
|
3806
|
-
const viewPath = ensureRelativePath3(asString4(
|
|
3807
|
-
const assetSetDir = (asOptionalString5(
|
|
3808
|
-
const sourceIcon = asOptionalString5(
|
|
3809
|
-
const appName = asOptionalString5(
|
|
3810
|
-
const brandHex = asOptionalString5(
|
|
3811
|
-
const durationMs = Math.max(0, Math.round(asOptionalNumber5(
|
|
4400
|
+
async run(input, context) {
|
|
4401
|
+
const viewPath = ensureRelativePath3(asString4(input, "viewPath"));
|
|
4402
|
+
const assetSetDir = (asOptionalString5(input, "assetSetDir") ?? inferIosSplashAssetSetDir(viewPath)).replace(/\/+$/, "");
|
|
4403
|
+
const sourceIcon = asOptionalString5(input, "sourceIcon");
|
|
4404
|
+
const appName = asOptionalString5(input, "appName") ?? "App";
|
|
4405
|
+
const brandHex = asOptionalString5(input, "brandHex") ?? "#000000";
|
|
4406
|
+
const durationMs = Math.max(0, Math.round(asOptionalNumber5(input, "durationMs", 1200)));
|
|
3812
4407
|
const durationNs = durationMs * 1e6;
|
|
3813
4408
|
const rgb = brandHex.replace("#", "").match(/.{1,2}/g)?.slice(0, 3).map((part) => Number.parseInt(part, 16)) ?? [0, 0, 0];
|
|
3814
4409
|
const view = [
|
|
@@ -3909,12 +4504,12 @@ var createAndroidSplashTool = {
|
|
|
3909
4504
|
}
|
|
3910
4505
|
}
|
|
3911
4506
|
},
|
|
3912
|
-
async run(
|
|
3913
|
-
const resDir = ensureRelativePath3(asString4(
|
|
3914
|
-
const sourceIcon = asOptionalString5(
|
|
3915
|
-
const brandHex = asOptionalString5(
|
|
3916
|
-
const themeName = asOptionalString5(
|
|
3917
|
-
const iconName = (asOptionalString5(
|
|
4507
|
+
async run(input, context) {
|
|
4508
|
+
const resDir = ensureRelativePath3(asString4(input, "resDir")).replace(/\/+$/, "");
|
|
4509
|
+
const sourceIcon = asOptionalString5(input, "sourceIcon");
|
|
4510
|
+
const brandHex = asOptionalString5(input, "brandHex") ?? "#000000";
|
|
4511
|
+
const themeName = asOptionalString5(input, "themeName") ?? "Theme.App.Starting";
|
|
4512
|
+
const iconName = (asOptionalString5(input, "iconName") ?? "ic_splash_logo").replace(/[^a-z0-9_]/gi, "_").toLowerCase();
|
|
3918
4513
|
const valuesPath = `${resDir}/values/splash_theme.xml`;
|
|
3919
4514
|
const drawablePath = `${resDir}/drawable/${iconName}.png`;
|
|
3920
4515
|
const xml = [
|
|
@@ -3964,12 +4559,12 @@ var generateAppIconsTool = {
|
|
|
3964
4559
|
}
|
|
3965
4560
|
}
|
|
3966
4561
|
},
|
|
3967
|
-
async run(
|
|
3968
|
-
const source = ensureRelativePath3(asString4(
|
|
3969
|
-
const appleOutputDir = asOptionalString5(
|
|
3970
|
-
const androidResDir = asOptionalString5(
|
|
3971
|
-
const background = asOptionalString5(
|
|
3972
|
-
const rawApplePlatforms = asRecord5(
|
|
4562
|
+
async run(input, context) {
|
|
4563
|
+
const source = ensureRelativePath3(asString4(input, "source"));
|
|
4564
|
+
const appleOutputDir = asOptionalString5(input, "appleOutputDir");
|
|
4565
|
+
const androidResDir = asOptionalString5(input, "androidResDir");
|
|
4566
|
+
const background = asOptionalString5(input, "background") ?? "#ffffff";
|
|
4567
|
+
const rawApplePlatforms = asRecord5(input).applePlatforms;
|
|
3973
4568
|
const applePlatforms = Array.isArray(rawApplePlatforms) ? rawApplePlatforms.filter((item) => typeof item === "string" && item.trim().length > 0) : ["ios", "macos"];
|
|
3974
4569
|
const files = [];
|
|
3975
4570
|
const outputs = {};
|
|
@@ -3994,8 +4589,8 @@ var generateAppIconsTool = {
|
|
|
3994
4589
|
function packageToDir(packageName) {
|
|
3995
4590
|
return packageName.split(".").map((part) => part.replace(/[^A-Za-z0-9_]/g, "")).filter(Boolean).join("/");
|
|
3996
4591
|
}
|
|
3997
|
-
function kotlinIdentifier(
|
|
3998
|
-
const cleaned =
|
|
4592
|
+
function kotlinIdentifier(input, fallback) {
|
|
4593
|
+
const cleaned = input.replace(/[^A-Za-z0-9_]/g, "").replace(/^[0-9]+/, "");
|
|
3999
4594
|
return cleaned || fallback;
|
|
4000
4595
|
}
|
|
4001
4596
|
function addLineBeforeClosingPluginsBlock(gradle, line) {
|
|
@@ -4068,17 +4663,17 @@ var createAndroidFoundationTool = {
|
|
|
4068
4663
|
}
|
|
4069
4664
|
}
|
|
4070
4665
|
},
|
|
4071
|
-
async run(
|
|
4072
|
-
const packageName = asString4(
|
|
4073
|
-
const appName = asOptionalString5(
|
|
4074
|
-
const sourceRoot = ensureRelativePath3(asOptionalString5(
|
|
4075
|
-
const rootGradlePath = ensureRelativePath3(asOptionalString5(
|
|
4076
|
-
const moduleGradlePath = ensureRelativePath3(asOptionalString5(
|
|
4077
|
-
const brandPrimaryHex = (asOptionalString5(
|
|
4078
|
-
const brandSecondaryHex = (asOptionalString5(
|
|
4079
|
-
const updateGradle = asOptionalBoolean3(
|
|
4080
|
-
const overwriteExisting = asOptionalBoolean3(
|
|
4081
|
-
const preserveExisting = overwriteExisting ? false : asOptionalBoolean3(
|
|
4666
|
+
async run(input, context) {
|
|
4667
|
+
const packageName = asString4(input, "packageName").trim();
|
|
4668
|
+
const appName = asOptionalString5(input, "appName") ?? "App";
|
|
4669
|
+
const sourceRoot = ensureRelativePath3(asOptionalString5(input, "sourceRoot") ?? "app/src/main/java").replace(/\/+$/, "");
|
|
4670
|
+
const rootGradlePath = ensureRelativePath3(asOptionalString5(input, "rootGradlePath") ?? "build.gradle.kts");
|
|
4671
|
+
const moduleGradlePath = ensureRelativePath3(asOptionalString5(input, "moduleGradlePath") ?? "app/build.gradle.kts");
|
|
4672
|
+
const brandPrimaryHex = (asOptionalString5(input, "brandPrimaryHex") ?? "#A52A2A").replace(/^#?/, "0xFF");
|
|
4673
|
+
const brandSecondaryHex = (asOptionalString5(input, "brandSecondaryHex") ?? "#2D3748").replace(/^#?/, "0xFF");
|
|
4674
|
+
const updateGradle = asOptionalBoolean3(input, "updateGradle", true);
|
|
4675
|
+
const overwriteExisting = asOptionalBoolean3(input, "overwriteExisting", false);
|
|
4676
|
+
const preserveExisting = overwriteExisting ? false : asOptionalBoolean3(input, "preserveExisting", true);
|
|
4082
4677
|
const packageDir = packageToDir(packageName);
|
|
4083
4678
|
if (!packageDir) return { ok: false, summary: "Invalid package name.", error: "packageName must contain at least one valid package segment." };
|
|
4084
4679
|
const classPrefix = kotlinIdentifier(appName, "App");
|
|
@@ -4389,10 +4984,10 @@ var commitPlatformChangesTool = {
|
|
|
4389
4984
|
}
|
|
4390
4985
|
}
|
|
4391
4986
|
},
|
|
4392
|
-
async run(
|
|
4393
|
-
const message = asString4(
|
|
4394
|
-
const amend = asOptionalBoolean3(
|
|
4395
|
-
const record = asRecord5(
|
|
4987
|
+
async run(input, context) {
|
|
4988
|
+
const message = asString4(input, "message");
|
|
4989
|
+
const amend = asOptionalBoolean3(input, "amend", false);
|
|
4990
|
+
const record = asRecord5(input);
|
|
4396
4991
|
const rawPaths = Array.isArray(record.files) ? record.files : record.paths;
|
|
4397
4992
|
const paths = Array.isArray(rawPaths) ? rawPaths.filter((path) => typeof path === "string" && path.trim().length > 0).map(ensureRelativePath3) : [];
|
|
4398
4993
|
if (paths.length === 0) return { ok: false, summary: "No paths provided for commit.", error: "Provide at least one path to stage." };
|
|
@@ -4457,9 +5052,9 @@ var scanSecretsTool = {
|
|
|
4457
5052
|
}
|
|
4458
5053
|
}
|
|
4459
5054
|
},
|
|
4460
|
-
async run(
|
|
4461
|
-
const scanPath = asOptionalString5(
|
|
4462
|
-
const maxFiles = Math.min(asOptionalNumber5(
|
|
5055
|
+
async run(input, context) {
|
|
5056
|
+
const scanPath = asOptionalString5(input, "path");
|
|
5057
|
+
const maxFiles = Math.min(asOptionalNumber5(input, "maxFiles", 500), 2e3);
|
|
4463
5058
|
const root = scanPath ? resolveInsideWorkspace(context.workspace, ensureRelativePath3(scanPath)) : context.workspace;
|
|
4464
5059
|
const files = collectFiles2(root, maxFiles);
|
|
4465
5060
|
const findings = [];
|
|
@@ -4537,11 +5132,11 @@ var ToolRegistry = class {
|
|
|
4537
5132
|
get(name) {
|
|
4538
5133
|
return this.tools.get(name);
|
|
4539
5134
|
}
|
|
4540
|
-
run(tool,
|
|
5135
|
+
run(tool, input, context, options = {}) {
|
|
4541
5136
|
const runContext = { ...context };
|
|
4542
5137
|
if (options.onProgress) runContext.onProgress = options.onProgress;
|
|
4543
5138
|
if (options.signal) runContext.signal = options.signal;
|
|
4544
|
-
return tool.run(
|
|
5139
|
+
return tool.run(input, runContext);
|
|
4545
5140
|
}
|
|
4546
5141
|
};
|
|
4547
5142
|
|
|
@@ -5165,15 +5760,15 @@ function metadataBoolean(runContext, key) {
|
|
|
5165
5760
|
const value = runContext?.metadata?.[key];
|
|
5166
5761
|
return value === true || value === "true" || value === "yes";
|
|
5167
5762
|
}
|
|
5168
|
-
function
|
|
5763
|
+
function isRecord3(value) {
|
|
5169
5764
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
5170
5765
|
}
|
|
5171
5766
|
function metadataRecord(runContext, key) {
|
|
5172
5767
|
const value = runContext?.metadata?.[key];
|
|
5173
|
-
return
|
|
5768
|
+
return isRecord3(value) ? value : void 0;
|
|
5174
5769
|
}
|
|
5175
5770
|
function recordArray2(value) {
|
|
5176
|
-
return Array.isArray(value) ? value.filter(
|
|
5771
|
+
return Array.isArray(value) ? value.filter(isRecord3) : [];
|
|
5177
5772
|
}
|
|
5178
5773
|
function autoBriefRecords(runContext, key) {
|
|
5179
5774
|
return recordArray2(metadataRecord(runContext, "autoBrief")?.[key]);
|
|
@@ -7200,14 +7795,14 @@ function normalizeVerificationCommand(line) {
|
|
|
7200
7795
|
return line.replace(/^Verification:\s*/i, "").replace(/\s*->\s*(passed|failed|BUILD SUCCESSFUL|BUILD FAILED|blocked|.+)$/i, "").replace(/\s+2>&1\b/g, "").replace(/\s+/g, " ").trim();
|
|
7201
7796
|
}
|
|
7202
7797
|
function successfulVerificationCommands(text2) {
|
|
7203
|
-
const
|
|
7798
|
+
const commands2 = /* @__PURE__ */ new Set();
|
|
7204
7799
|
for (const line of text2.split(/\r?\n/)) {
|
|
7205
7800
|
if (!/^Verification:\s*/i.test(line)) continue;
|
|
7206
7801
|
if (!/->\s*(passed|BUILD SUCCESSFUL)\b/i.test(line)) continue;
|
|
7207
7802
|
const command = normalizeVerificationCommand(line);
|
|
7208
|
-
if (command)
|
|
7803
|
+
if (command) commands2.add(command);
|
|
7209
7804
|
}
|
|
7210
|
-
return
|
|
7805
|
+
return commands2;
|
|
7211
7806
|
}
|
|
7212
7807
|
function hasSuccessfulVerification2(verificationLines, pattern) {
|
|
7213
7808
|
return verificationLines.some((line) => /->\s*passed\b/i.test(line) && pattern.test(line));
|
|
@@ -7719,9 +8314,9 @@ ${content}`);
|
|
|
7719
8314
|
for (const { path, content } of indexReads) {
|
|
7720
8315
|
lines.push("", `### ${path}`, content.trim());
|
|
7721
8316
|
}
|
|
7722
|
-
const
|
|
7723
|
-
return
|
|
7724
|
-
[... truncated]` :
|
|
8317
|
+
const output = lines.join("\n");
|
|
8318
|
+
return output.length > 18e3 ? `${output.slice(0, 17980)}
|
|
8319
|
+
[... truncated]` : output;
|
|
7725
8320
|
}
|
|
7726
8321
|
function runGit(workspace, args) {
|
|
7727
8322
|
const result = spawnSync2("git", args, {
|
|
@@ -7846,8 +8441,8 @@ function buildExportMap(workspace) {
|
|
|
7846
8441
|
if (totalExportingFiles > entries.length) {
|
|
7847
8442
|
lines.push(`[... ${totalExportingFiles - entries.length} more files]`);
|
|
7848
8443
|
}
|
|
7849
|
-
let
|
|
7850
|
-
if (
|
|
8444
|
+
let output = lines.join("\n");
|
|
8445
|
+
if (output.length <= 3e3) return output;
|
|
7851
8446
|
const truncatedLines = ["## Workspace export map"];
|
|
7852
8447
|
let included = 0;
|
|
7853
8448
|
for (const line of lines.slice(1)) {
|
|
@@ -7859,9 +8454,9 @@ function buildExportMap(workspace) {
|
|
|
7859
8454
|
}
|
|
7860
8455
|
const omitted = entries.length - included + Math.max(0, totalExportingFiles - entries.length);
|
|
7861
8456
|
if (omitted > 0) truncatedLines.push(`[... ${omitted} more files]`);
|
|
7862
|
-
|
|
7863
|
-
return
|
|
7864
|
-
[... truncated]` :
|
|
8457
|
+
output = truncatedLines.join("\n");
|
|
8458
|
+
return output.length > 3e3 ? `${output.slice(0, 2980)}
|
|
8459
|
+
[... truncated]` : output;
|
|
7865
8460
|
}
|
|
7866
8461
|
function detectVerificationSuggestions(workspace, projectTypes, packageScripts) {
|
|
7867
8462
|
const suggestions = /* @__PURE__ */ new Set();
|
|
@@ -8469,6 +9064,7 @@ function loadSkillPacksFromRoot(ctx, skillsRoot) {
|
|
|
8469
9064
|
return enforceBudget(matched).map((pack) => ({
|
|
8470
9065
|
slug: pack.frontmatter.slug,
|
|
8471
9066
|
title: pack.title,
|
|
9067
|
+
sourcePath: join13(skillsRoot, `${pack.relativePath}.md`),
|
|
8472
9068
|
content: pack.body,
|
|
8473
9069
|
tokens: pack.tokens,
|
|
8474
9070
|
reason: pack.reason
|
|
@@ -8478,6 +9074,19 @@ function loadSkillPacks(ctx) {
|
|
|
8478
9074
|
return loadSkillPacksFromRoot(ctx, resolveDefaultSkillsRoot());
|
|
8479
9075
|
}
|
|
8480
9076
|
|
|
9077
|
+
// src/skills/summary.ts
|
|
9078
|
+
function formatSkillPackSummary(packs) {
|
|
9079
|
+
const totalTokens = packs.reduce((sum, pack) => sum + pack.tokens, 0);
|
|
9080
|
+
const lines = [
|
|
9081
|
+
`Skill packs loaded: ${packs.length}`,
|
|
9082
|
+
"| slug | source | reason | tokens |",
|
|
9083
|
+
"|------|--------|--------|--------|",
|
|
9084
|
+
...packs.map((pack) => `| ${pack.slug} | ${pack.sourcePath} | ${pack.reason} | ${pack.tokens} |`),
|
|
9085
|
+
`Total pack tokens: ${totalTokens} / ${SKILL_PACK_TOKEN_BUDGET}`
|
|
9086
|
+
];
|
|
9087
|
+
return lines.join("\n");
|
|
9088
|
+
}
|
|
9089
|
+
|
|
8481
9090
|
// src/agent/systemPrompt.ts
|
|
8482
9091
|
function readProjectInstructions(workspace) {
|
|
8483
9092
|
const path = join14(workspace, ".tania", "INSTRUCTIONS.md");
|
|
@@ -8584,25 +9193,6 @@ import { cp as cp2, mkdir as mkdir10, rm as rm2, stat as stat6 } from "fs/promis
|
|
|
8584
9193
|
import { dirname as dirname12, isAbsolute as isAbsolute3, join as join15, relative as relative9, resolve as resolve9 } from "path";
|
|
8585
9194
|
var CONTEXT_TOKEN_LIMIT = 48e3;
|
|
8586
9195
|
var CONTEXT_SUMMARY_KEEP_RECENT = 6;
|
|
8587
|
-
function isMalformedToolArguments(value) {
|
|
8588
|
-
return Boolean(value && typeof value === "object" && value.ok === false && typeof value.rawArguments === "string");
|
|
8589
|
-
}
|
|
8590
|
-
function previewRawToolArguments(raw) {
|
|
8591
|
-
return raw.length > 500 ? `${raw.slice(0, 500)}...[truncated ${raw.length - 500} chars]` : raw;
|
|
8592
|
-
}
|
|
8593
|
-
function parseToolArguments(raw) {
|
|
8594
|
-
if (!raw.trim()) return {};
|
|
8595
|
-
try {
|
|
8596
|
-
return JSON.parse(raw);
|
|
8597
|
-
} catch {
|
|
8598
|
-
const rawPreview = previewRawToolArguments(raw);
|
|
8599
|
-
return {
|
|
8600
|
-
ok: false,
|
|
8601
|
-
error: `malformed tool arguments: ${rawPreview}`,
|
|
8602
|
-
rawArguments: rawPreview
|
|
8603
|
-
};
|
|
8604
|
-
}
|
|
8605
|
-
}
|
|
8606
9196
|
function findSafeCompressionBoundary(messages, desiredKeepCount) {
|
|
8607
9197
|
if (messages.length <= desiredKeepCount + 1) return Math.max(1, messages.length - desiredKeepCount);
|
|
8608
9198
|
let startIndex = messages.length - desiredKeepCount;
|
|
@@ -8624,10 +9214,10 @@ function fieldMatchesType(value, expectedType) {
|
|
|
8624
9214
|
if (expectedType === "array") return Array.isArray(value);
|
|
8625
9215
|
return typeof value === expectedType;
|
|
8626
9216
|
}
|
|
8627
|
-
function validateToolInput(
|
|
9217
|
+
function validateToolInput(input, definition) {
|
|
8628
9218
|
const params = definition.function.parameters;
|
|
8629
9219
|
if (!params) return null;
|
|
8630
|
-
const record =
|
|
9220
|
+
const record = input && typeof input === "object" ? input : {};
|
|
8631
9221
|
for (const key of params.required ?? []) {
|
|
8632
9222
|
if (!(key in record) || record[key] === void 0 || record[key] === null) {
|
|
8633
9223
|
return `Missing required field: "${key}"`;
|
|
@@ -8706,6 +9296,7 @@ function logRunSummarySilently(params) {
|
|
|
8706
9296
|
{
|
|
8707
9297
|
ts,
|
|
8708
9298
|
prompt: params.prompt.slice(0, 200),
|
|
9299
|
+
provider: params.provider,
|
|
8709
9300
|
model: params.model,
|
|
8710
9301
|
durationMs: params.metrics.durationMs,
|
|
8711
9302
|
promptTokens: params.metrics.promptTokens,
|
|
@@ -8853,8 +9444,8 @@ function repairAttemptSnapshot(attempt, manifest) {
|
|
|
8853
9444
|
changedFileCount: manifest.changedFiles.length
|
|
8854
9445
|
};
|
|
8855
9446
|
}
|
|
8856
|
-
function commandLabel(toolName,
|
|
8857
|
-
const record =
|
|
9447
|
+
function commandLabel(toolName, input) {
|
|
9448
|
+
const record = input && typeof input === "object" ? input : {};
|
|
8858
9449
|
if (toolName === "run_shell") {
|
|
8859
9450
|
const script = typeof record.script === "string" ? record.script.trim() : typeof record.command === "string" ? record.command.trim() : "";
|
|
8860
9451
|
return script || null;
|
|
@@ -8902,9 +9493,9 @@ function requiredHighLevelTool(runContext, prompt = "") {
|
|
|
8902
9493
|
}
|
|
8903
9494
|
return null;
|
|
8904
9495
|
}
|
|
8905
|
-
function toolCallMayMutate(toolName,
|
|
9496
|
+
function toolCallMayMutate(toolName, input) {
|
|
8906
9497
|
if (mutatingToolNames.has(toolName)) return true;
|
|
8907
|
-
const record =
|
|
9498
|
+
const record = input && typeof input === "object" ? input : {};
|
|
8908
9499
|
if (toolName === "run_shell") {
|
|
8909
9500
|
const script = typeof record.script === "string" ? record.script : "";
|
|
8910
9501
|
return /\b(?:cat|printf|echo)\b[\s\S]{0,200}>\s*[^&|;\n]|\btee\s+[^|;\n]+|\b(?:mkdir|touch|rm|mv|cp)\s+|\bsed\s+-i\b|\bperl\s+-pi\b|\bktlintFormat\b/.test(script);
|
|
@@ -8931,27 +9522,27 @@ function verificationKey(label) {
|
|
|
8931
9522
|
if (/\bgit\s+rev-parse\s+--short\s+HEAD\b/i.test(label)) return "git head";
|
|
8932
9523
|
return label.replace(/\s+/g, " ").trim();
|
|
8933
9524
|
}
|
|
8934
|
-
function artifactPathFromRead(toolName,
|
|
9525
|
+
function artifactPathFromRead(toolName, input) {
|
|
8935
9526
|
if (toolName !== "read_file") return null;
|
|
8936
|
-
const record =
|
|
9527
|
+
const record = input && typeof input === "object" ? input : {};
|
|
8937
9528
|
const path = typeof record.path === "string" ? record.path.trim() : "";
|
|
8938
9529
|
if (!path) return null;
|
|
8939
9530
|
if (path.startsWith(".tania/artifacts/")) return path;
|
|
8940
9531
|
if (path.startsWith("artifacts/")) return path;
|
|
8941
9532
|
return null;
|
|
8942
9533
|
}
|
|
8943
|
-
function contextPathFromRead(toolName,
|
|
9534
|
+
function contextPathFromRead(toolName, input, runContext) {
|
|
8944
9535
|
if (toolName !== "read_file") return null;
|
|
8945
|
-
const record =
|
|
9536
|
+
const record = input && typeof input === "object" ? input : {};
|
|
8946
9537
|
const path = typeof record.path === "string" ? record.path.trim() : "";
|
|
8947
9538
|
if (!path) return null;
|
|
8948
9539
|
if (path.startsWith(".tania/context/")) return path;
|
|
8949
9540
|
if ((runContext?.contextFiles ?? []).some((contextFile) => contextFile.path === path)) return path;
|
|
8950
9541
|
return null;
|
|
8951
9542
|
}
|
|
8952
|
-
function outsideWorkspaceReadMessage(workspace, toolName,
|
|
9543
|
+
function outsideWorkspaceReadMessage(workspace, toolName, input) {
|
|
8953
9544
|
if (toolName !== "read_file") return null;
|
|
8954
|
-
const record =
|
|
9545
|
+
const record = input && typeof input === "object" ? input : {};
|
|
8955
9546
|
const path = typeof record.path === "string" ? record.path.trim() : "";
|
|
8956
9547
|
if (!path || !isAbsolute3(path)) return null;
|
|
8957
9548
|
const target = resolve9(path);
|
|
@@ -9000,6 +9591,7 @@ async function runAgent(options) {
|
|
|
9000
9591
|
let createdArtifactPaths = [];
|
|
9001
9592
|
const requiredTool = requiredHighLevelTool(options.runContext, options.prompt);
|
|
9002
9593
|
let requiredToolUsed = requiredTool ? false : true;
|
|
9594
|
+
let toolCallCorrectionAttempts = 0;
|
|
9003
9595
|
async function syncArtifactOutput() {
|
|
9004
9596
|
const outputRootValue = options.runContext?.metadata?.artifactOutputRoot;
|
|
9005
9597
|
if (typeof outputRootValue !== "string" || !outputRootValue.trim()) return [];
|
|
@@ -9045,6 +9637,7 @@ async function runAgent(options) {
|
|
|
9045
9637
|
logRunSummarySilently({
|
|
9046
9638
|
workspace,
|
|
9047
9639
|
prompt: options.prompt,
|
|
9640
|
+
provider: options.provider.id,
|
|
9048
9641
|
model: options.provider.model,
|
|
9049
9642
|
metrics,
|
|
9050
9643
|
manifest: manifest2
|
|
@@ -9067,7 +9660,7 @@ async function runAgent(options) {
|
|
|
9067
9660
|
}
|
|
9068
9661
|
await options.sink({ type: "message_start" });
|
|
9069
9662
|
let assistantText = "";
|
|
9070
|
-
let
|
|
9663
|
+
let rawToolCalls = [];
|
|
9071
9664
|
const codingProviderOptions = isCodingTask(options.runContext) ? { temperature: 0, topP: 0.2 } : {};
|
|
9072
9665
|
let providerAttempt = 0;
|
|
9073
9666
|
const PROVIDER_TRANSIENT_RETRIES = 1;
|
|
@@ -9076,24 +9669,44 @@ async function runAgent(options) {
|
|
|
9076
9669
|
for await (const delta of options.provider.streamChat({
|
|
9077
9670
|
messages,
|
|
9078
9671
|
tools: registry.list().map((tool) => tool.definition),
|
|
9672
|
+
onProviderThrottle: (event) => {
|
|
9673
|
+
void Promise.resolve(options.sink({
|
|
9674
|
+
type: "provider_throttle",
|
|
9675
|
+
provider: event.provider,
|
|
9676
|
+
attempt: event.attempt,
|
|
9677
|
+
waitMs: event.waitMs
|
|
9678
|
+
})).catch(() => {
|
|
9679
|
+
});
|
|
9680
|
+
},
|
|
9079
9681
|
...codingProviderOptions
|
|
9080
9682
|
})) {
|
|
9081
9683
|
if (delta.usage) {
|
|
9082
9684
|
totalPromptTokens += delta.usage.promptTokens;
|
|
9083
9685
|
totalCompletionTokens += delta.usage.completionTokens;
|
|
9084
9686
|
}
|
|
9687
|
+
if (delta.schemaWarnings) {
|
|
9688
|
+
for (const warning of delta.schemaWarnings) {
|
|
9689
|
+
await options.sink({
|
|
9690
|
+
type: "schema_flatten_warning",
|
|
9691
|
+
reason: warning.reason,
|
|
9692
|
+
path: warning.path,
|
|
9693
|
+
provider: options.provider.id,
|
|
9694
|
+
...warning.tool ? { tool: warning.tool } : {}
|
|
9695
|
+
});
|
|
9696
|
+
}
|
|
9697
|
+
}
|
|
9085
9698
|
if (delta.content) {
|
|
9086
9699
|
assistantText += delta.content;
|
|
9087
9700
|
finalText += delta.content;
|
|
9088
9701
|
await options.sink({ type: "message_delta", text: delta.content });
|
|
9089
9702
|
}
|
|
9090
|
-
if (delta.toolCalls?.length)
|
|
9703
|
+
if (delta.toolCalls?.length) rawToolCalls = delta.toolCalls;
|
|
9091
9704
|
}
|
|
9092
9705
|
break;
|
|
9093
9706
|
} catch (err) {
|
|
9094
9707
|
const message2 = err instanceof Error ? err.message : String(err);
|
|
9095
9708
|
const isTransient = /timed out|fetch failed|ECONNRESET|EAI_AGAIN|ENOTFOUND|socket hang up/i.test(message2);
|
|
9096
|
-
const noProgressYet = assistantText.length === 0 &&
|
|
9709
|
+
const noProgressYet = assistantText.length === 0 && rawToolCalls.length === 0;
|
|
9097
9710
|
if (isTransient && noProgressYet && providerAttempt < PROVIDER_TRANSIENT_RETRIES) {
|
|
9098
9711
|
providerAttempt += 1;
|
|
9099
9712
|
await options.sink({ type: "status", message: `Provider transient error (${message2.slice(0, 120)}); retrying same turn (${providerAttempt}/${PROVIDER_TRANSIENT_RETRIES}).` });
|
|
@@ -9103,6 +9716,62 @@ async function runAgent(options) {
|
|
|
9103
9716
|
}
|
|
9104
9717
|
}
|
|
9105
9718
|
await options.sink({ type: "message_end" });
|
|
9719
|
+
const parsedToolCalls = rawToolCalls.length > 0 ? parseProviderToolCalls(rawToolCalls, { turn }) : { toolCalls: [], warnings: [], failures: [] };
|
|
9720
|
+
for (const warning of parsedToolCalls.warnings) {
|
|
9721
|
+
await options.sink({
|
|
9722
|
+
type: "tool_call_parse_warning",
|
|
9723
|
+
reason: warning.reason,
|
|
9724
|
+
provider: options.provider.id,
|
|
9725
|
+
turn,
|
|
9726
|
+
attempt: toolCallCorrectionAttempts,
|
|
9727
|
+
...warning.toolCallId ? { toolCallId: warning.toolCallId } : {},
|
|
9728
|
+
...warning.tool ? { tool: warning.tool } : {}
|
|
9729
|
+
});
|
|
9730
|
+
}
|
|
9731
|
+
for (const failure of parsedToolCalls.failures) {
|
|
9732
|
+
await options.sink({
|
|
9733
|
+
type: "tool_call_parse_warning",
|
|
9734
|
+
reason: failure.reason,
|
|
9735
|
+
provider: options.provider.id,
|
|
9736
|
+
turn,
|
|
9737
|
+
attempt: toolCallCorrectionAttempts + 1,
|
|
9738
|
+
toolCallId: failure.toolCall.id,
|
|
9739
|
+
tool: failure.toolCall.function.name
|
|
9740
|
+
});
|
|
9741
|
+
}
|
|
9742
|
+
if (parsedToolCalls.failures.length > 0 && toolCallCorrectionAttempts < TOOL_CALL_CORRECTION_LIMIT && turn < maxTurns - 1) {
|
|
9743
|
+
toolCallCorrectionAttempts += 1;
|
|
9744
|
+
messages.push({ role: "assistant", content: assistantText || null });
|
|
9745
|
+
messages.push({
|
|
9746
|
+
role: "user",
|
|
9747
|
+
content: malformedToolCallCorrectionMessage(parsedToolCalls.failures.map((failure) => failure.reason).join("; "))
|
|
9748
|
+
});
|
|
9749
|
+
continue;
|
|
9750
|
+
}
|
|
9751
|
+
if (parsedToolCalls.failures.length > 0) {
|
|
9752
|
+
const failedToolCalls = parsedToolCalls.failures.map((failure) => failure.toolCall);
|
|
9753
|
+
toolErrorCount += failedToolCalls.length;
|
|
9754
|
+
messages.push({
|
|
9755
|
+
role: "assistant",
|
|
9756
|
+
content: assistantText || null,
|
|
9757
|
+
tool_calls: failedToolCalls
|
|
9758
|
+
});
|
|
9759
|
+
for (const failure of parsedToolCalls.failures) {
|
|
9760
|
+
const error = `malformed tool call after ${TOOL_CALL_CORRECTION_LIMIT} correction attempts: ${failure.reason}`;
|
|
9761
|
+
messages.push({ role: "tool", tool_call_id: failure.toolCall.id, content: JSON.stringify({ ok: false, error }) });
|
|
9762
|
+
await options.sink({
|
|
9763
|
+
type: "tool_result",
|
|
9764
|
+
id: failure.toolCall.id,
|
|
9765
|
+
tool: failure.toolCall.function.name,
|
|
9766
|
+
ok: false,
|
|
9767
|
+
summary: "Malformed tool call after correction attempts.",
|
|
9768
|
+
error
|
|
9769
|
+
});
|
|
9770
|
+
}
|
|
9771
|
+
continue;
|
|
9772
|
+
}
|
|
9773
|
+
const toolCalls = parsedToolCalls.toolCalls;
|
|
9774
|
+
if (toolCalls.length > 0) toolCallCorrectionAttempts = 0;
|
|
9106
9775
|
const assistantMessage = {
|
|
9107
9776
|
role: "assistant",
|
|
9108
9777
|
content: assistantText || null
|
|
@@ -9169,16 +9838,16 @@ async function runAgent(options) {
|
|
|
9169
9838
|
for (const toolCall2 of toolCalls) {
|
|
9170
9839
|
const toolName = toolCall2.function.name;
|
|
9171
9840
|
const tool = registry.get(toolName);
|
|
9172
|
-
const
|
|
9173
|
-
if (
|
|
9841
|
+
const parsedInput = parseToolArguments(toolCall2.function.arguments);
|
|
9842
|
+
if (!parsedInput.ok) {
|
|
9174
9843
|
toolErrorCount += 1;
|
|
9175
9844
|
messages.push({
|
|
9176
9845
|
role: "tool",
|
|
9177
9846
|
tool_call_id: toolCall2.id,
|
|
9178
9847
|
content: JSON.stringify({
|
|
9179
9848
|
ok: false,
|
|
9180
|
-
error:
|
|
9181
|
-
rawArguments:
|
|
9849
|
+
error: parsedInput.reason,
|
|
9850
|
+
rawArguments: parsedInput.rawArguments
|
|
9182
9851
|
})
|
|
9183
9852
|
});
|
|
9184
9853
|
await options.sink({
|
|
@@ -9187,11 +9856,12 @@ async function runAgent(options) {
|
|
|
9187
9856
|
tool: toolName,
|
|
9188
9857
|
ok: false,
|
|
9189
9858
|
summary: "Invalid tool arguments (malformed JSON).",
|
|
9190
|
-
output: `raw arguments (preview): ${
|
|
9191
|
-
error:
|
|
9859
|
+
output: `raw arguments (preview): ${parsedInput.rawArguments}`,
|
|
9860
|
+
error: parsedInput.reason
|
|
9192
9861
|
});
|
|
9193
9862
|
continue;
|
|
9194
9863
|
}
|
|
9864
|
+
const callInput = parsedInput.input;
|
|
9195
9865
|
toolCallCount += 1;
|
|
9196
9866
|
await options.sink({ type: "tool_call", id: toolCall2.id, tool: toolName, input: callInput });
|
|
9197
9867
|
if (!tool) {
|
|
@@ -9431,12 +10101,12 @@ function runCommand(cmd, args, cwd, timeoutMs) {
|
|
|
9431
10101
|
timeout: timeoutMs,
|
|
9432
10102
|
shell: false
|
|
9433
10103
|
});
|
|
9434
|
-
const
|
|
10104
|
+
const output = `${result.stdout ?? ""}${result.stderr ?? ""}`.trim();
|
|
9435
10105
|
const exitCode = result.status ?? (result.error ? 1 : 0);
|
|
9436
|
-
return { exitCode, output
|
|
10106
|
+
return { exitCode, output };
|
|
9437
10107
|
}
|
|
9438
|
-
function parseTypeScriptErrors(
|
|
9439
|
-
return
|
|
10108
|
+
function parseTypeScriptErrors(output) {
|
|
10109
|
+
return output.split(/\r?\n/).filter((line) => /\.tsx?:\d+:\d+\s*-?\s*error\s+TS\d+:/i.test(line) || /error TS\d+:/i.test(line)).slice(0, 5);
|
|
9440
10110
|
}
|
|
9441
10111
|
async function detectPostRunBlockers(cwd, manifest) {
|
|
9442
10112
|
const blockers = [];
|
|
@@ -9445,9 +10115,9 @@ async function detectPostRunBlockers(cwd, manifest) {
|
|
|
9445
10115
|
const packageManager = readPackageManager(cwd);
|
|
9446
10116
|
if (tsconfigExists && !hasPassingVerification(manifest, /tsc|typecheck|type-check/i)) {
|
|
9447
10117
|
const typecheckCommand = scripts.typecheck ? packageScriptCommand(packageManager, "typecheck") : { cmd: "npx", args: ["tsc", "--noEmit", "--pretty", "false"] };
|
|
9448
|
-
const { exitCode, output
|
|
10118
|
+
const { exitCode, output } = runCommand(typecheckCommand.cmd, typecheckCommand.args, cwd, POST_CHECK_TIMEOUT_MS);
|
|
9449
10119
|
if (exitCode !== 0) {
|
|
9450
|
-
const errorLines = parseTypeScriptErrors(
|
|
10120
|
+
const errorLines = parseTypeScriptErrors(output);
|
|
9451
10121
|
const summary = errorLines.length > 0 ? `TypeScript errors after run:
|
|
9452
10122
|
${errorLines.map((line) => ` ${line}`).join("\n")}` : "TypeScript compilation failed (post-run check)";
|
|
9453
10123
|
blockers.push(summary);
|
|
@@ -9540,11 +10210,424 @@ ${diff.slice(0, 8e3)}
|
|
|
9540
10210
|
|
|
9541
10211
|
// src/agent/chat.ts
|
|
9542
10212
|
import { createInterface } from "readline/promises";
|
|
9543
|
-
import { stdin as
|
|
10213
|
+
import { stdin as processInput, stdout as processOutput } from "process";
|
|
10214
|
+
|
|
10215
|
+
// src/commands/registry.ts
|
|
10216
|
+
var commands = /* @__PURE__ */ new Map();
|
|
10217
|
+
function registerCommand(command) {
|
|
10218
|
+
const name = normalizeCommandName(command.name);
|
|
10219
|
+
if (!name) throw new Error("Command name cannot be empty.");
|
|
10220
|
+
commands.set(name, { ...command, name });
|
|
10221
|
+
}
|
|
10222
|
+
function getCommand(name) {
|
|
10223
|
+
return commands.get(normalizeCommandName(name));
|
|
10224
|
+
}
|
|
10225
|
+
function listCommands() {
|
|
10226
|
+
return [...commands.values()].sort((a, b) => {
|
|
10227
|
+
const category = (a.category ?? "built-in").localeCompare(b.category ?? "built-in");
|
|
10228
|
+
return category || a.name.localeCompare(b.name);
|
|
10229
|
+
});
|
|
10230
|
+
}
|
|
10231
|
+
function removeCommandsByCategory(category) {
|
|
10232
|
+
for (const [name, command] of commands) {
|
|
10233
|
+
if (command.category === category) commands.delete(name);
|
|
10234
|
+
}
|
|
10235
|
+
}
|
|
10236
|
+
async function commandIsAvailable(command, ctx) {
|
|
10237
|
+
return command.availability ? await command.availability(ctx) : true;
|
|
10238
|
+
}
|
|
10239
|
+
function normalizeCommandName(name) {
|
|
10240
|
+
return name.trim().replace(/^\//, "").toLowerCase();
|
|
10241
|
+
}
|
|
10242
|
+
|
|
10243
|
+
// src/commands/builtin/clear.ts
|
|
10244
|
+
var clearCommand = {
|
|
10245
|
+
name: "clear",
|
|
10246
|
+
description: "Clear the active conversation history.",
|
|
10247
|
+
category: "built-in",
|
|
10248
|
+
handler(_args, ctx) {
|
|
10249
|
+
if (ctx.clearHistory) {
|
|
10250
|
+
ctx.clearHistory();
|
|
10251
|
+
} else if (ctx.history) {
|
|
10252
|
+
ctx.history.length = 0;
|
|
10253
|
+
}
|
|
10254
|
+
ctx.output.write("Conversation history cleared.\n");
|
|
10255
|
+
}
|
|
10256
|
+
};
|
|
10257
|
+
registerCommand(clearCommand);
|
|
10258
|
+
|
|
10259
|
+
// src/memory/runLogs.ts
|
|
10260
|
+
import { existsSync as existsSync15, readdirSync as readdirSync5, readFileSync as readFileSync7 } from "fs";
|
|
10261
|
+
import { join as join17 } from "path";
|
|
10262
|
+
var deepSeekPricingByModel = {
|
|
10263
|
+
"deepseek-chat": { inputPerMillion: 0.27, outputPerMillion: 1.1 },
|
|
10264
|
+
"deepseek-reasoner": { inputPerMillion: 0.55, outputPerMillion: 2.19 }
|
|
10265
|
+
};
|
|
10266
|
+
function readRunLogs(workspace, limit) {
|
|
10267
|
+
const runsDir = join17(workspace, ".tania", "runs");
|
|
10268
|
+
if (!existsSync15(runsDir)) return [];
|
|
10269
|
+
const files = readdirSync5(runsDir).filter((file) => file.endsWith(".json")).sort().reverse();
|
|
10270
|
+
const selected = limit === void 0 ? files : files.slice(0, limit);
|
|
10271
|
+
return selected.flatMap((file) => {
|
|
10272
|
+
try {
|
|
10273
|
+
const parsed = JSON.parse(readFileSync7(join17(runsDir, file), "utf8"));
|
|
10274
|
+
if (typeof parsed.ts !== "string" || typeof parsed.model !== "string") return [];
|
|
10275
|
+
return [{
|
|
10276
|
+
ts: parsed.ts,
|
|
10277
|
+
prompt: typeof parsed.prompt === "string" ? parsed.prompt : "",
|
|
10278
|
+
...typeof parsed.provider === "string" ? { provider: parsed.provider } : {},
|
|
10279
|
+
model: parsed.model,
|
|
10280
|
+
durationMs: typeof parsed.durationMs === "number" ? parsed.durationMs : 0,
|
|
10281
|
+
promptTokens: typeof parsed.promptTokens === "number" ? parsed.promptTokens : 0,
|
|
10282
|
+
completionTokens: typeof parsed.completionTokens === "number" ? parsed.completionTokens : 0,
|
|
10283
|
+
changedFiles: Array.isArray(parsed.changedFiles) ? parsed.changedFiles.filter((file2) => typeof file2 === "string") : [],
|
|
10284
|
+
blockers: Array.isArray(parsed.blockers) ? parsed.blockers.filter((blocker) => typeof blocker === "string") : []
|
|
10285
|
+
}];
|
|
10286
|
+
} catch {
|
|
10287
|
+
return [];
|
|
10288
|
+
}
|
|
10289
|
+
});
|
|
10290
|
+
}
|
|
10291
|
+
function estimateRunCost(log) {
|
|
10292
|
+
const provider = normalizeProvider(log.provider, log.model);
|
|
10293
|
+
const pricing = provider === "deepseek" ? deepSeekPricingByModel[log.model] : void 0;
|
|
10294
|
+
if (!pricing) {
|
|
10295
|
+
return { provider, usd: null, display: "pricing unknown" };
|
|
10296
|
+
}
|
|
10297
|
+
const usd = log.promptTokens / 1e6 * pricing.inputPerMillion + log.completionTokens / 1e6 * pricing.outputPerMillion;
|
|
10298
|
+
return { provider, usd, display: formatUsd(usd) };
|
|
10299
|
+
}
|
|
10300
|
+
function formatRunLogLine(log) {
|
|
10301
|
+
const cost = estimateRunCost(log);
|
|
10302
|
+
const status = log.blockers.length > 0 ? "BLOCKED" : "OK";
|
|
10303
|
+
const duration = `${Math.round(log.durationMs / 1e3)}s`;
|
|
10304
|
+
const fileCount = log.changedFiles.length;
|
|
10305
|
+
return `${log.ts.slice(0, 16)} ${status.padEnd(7)} ${duration.padStart(5)} ${cost.display.padStart(15)} ${fileCount} file(s) ${log.prompt.slice(0, 60)}`;
|
|
10306
|
+
}
|
|
10307
|
+
function formatUsd(usd) {
|
|
10308
|
+
if (usd < 1e-3) return "<$0.001";
|
|
10309
|
+
return `$${usd.toFixed(3)}`;
|
|
10310
|
+
}
|
|
10311
|
+
function normalizeProvider(provider, model) {
|
|
10312
|
+
if (provider?.trim()) return provider.trim();
|
|
10313
|
+
if (model.startsWith("deepseek-")) return "deepseek";
|
|
10314
|
+
return "unknown";
|
|
10315
|
+
}
|
|
10316
|
+
|
|
10317
|
+
// src/commands/builtin/cost.ts
|
|
10318
|
+
var costCommand = {
|
|
10319
|
+
name: "cost",
|
|
10320
|
+
description: "Show token usage and estimated run costs.",
|
|
10321
|
+
category: "built-in",
|
|
10322
|
+
handler(_args, ctx) {
|
|
10323
|
+
const logs = readRunLogs(ctx.cwd);
|
|
10324
|
+
if (logs.length === 0) {
|
|
10325
|
+
ctx.output.write("No run logs found. Run tanya run first.\n");
|
|
10326
|
+
return;
|
|
10327
|
+
}
|
|
10328
|
+
let knownTotal = 0;
|
|
10329
|
+
let unknownCount = 0;
|
|
10330
|
+
ctx.output.write("Recent run costs:\n");
|
|
10331
|
+
for (const log of logs) {
|
|
10332
|
+
const estimate = estimateRunCost(log);
|
|
10333
|
+
if (estimate.usd === null) {
|
|
10334
|
+
unknownCount += 1;
|
|
10335
|
+
} else {
|
|
10336
|
+
knownTotal += estimate.usd;
|
|
10337
|
+
}
|
|
10338
|
+
ctx.output.write(
|
|
10339
|
+
`${log.ts.slice(0, 16)} ${estimate.provider}:${log.model} ${log.promptTokens.toLocaleString()} in / ${log.completionTokens.toLocaleString()} out ${estimate.display}
|
|
10340
|
+
`
|
|
10341
|
+
);
|
|
10342
|
+
}
|
|
10343
|
+
ctx.output.write(`Session total: ${formatUsd(knownTotal)}${unknownCount > 0 ? ` (${unknownCount} run${unknownCount === 1 ? "" : "s"} pricing unknown)` : ""}
|
|
10344
|
+
`);
|
|
10345
|
+
}
|
|
10346
|
+
};
|
|
10347
|
+
registerCommand(costCommand);
|
|
10348
|
+
|
|
10349
|
+
// src/commands/builtin/help.ts
|
|
10350
|
+
var helpCommand = {
|
|
10351
|
+
name: "help",
|
|
10352
|
+
description: "List available slash commands.",
|
|
10353
|
+
category: "built-in",
|
|
10354
|
+
async handler(_args, ctx) {
|
|
10355
|
+
const commands2 = [];
|
|
10356
|
+
for (const command of listCommands()) {
|
|
10357
|
+
if (await commandIsAvailable(command, ctx)) commands2.push(command);
|
|
10358
|
+
}
|
|
10359
|
+
ctx.output.write("Built-in commands:\n");
|
|
10360
|
+
for (const command of commands2.filter((candidate) => (candidate.category ?? "built-in") === "built-in")) {
|
|
10361
|
+
ctx.output.write(`/${command.name} \u2014 ${command.description}
|
|
10362
|
+
`);
|
|
10363
|
+
}
|
|
10364
|
+
const projectCommands = commands2.filter((candidate) => candidate.category === "project");
|
|
10365
|
+
if (projectCommands.length > 0) {
|
|
10366
|
+
ctx.output.write("Project commands:\n");
|
|
10367
|
+
for (const command of projectCommands) {
|
|
10368
|
+
ctx.output.write(`/${command.name} \u2014 ${command.description}
|
|
10369
|
+
`);
|
|
10370
|
+
}
|
|
10371
|
+
}
|
|
10372
|
+
}
|
|
10373
|
+
};
|
|
10374
|
+
registerCommand(helpCommand);
|
|
10375
|
+
|
|
10376
|
+
// src/commands/builtin/memory.ts
|
|
10377
|
+
var DEFAULT_LIMIT = 10;
|
|
10378
|
+
var memoryCommand = {
|
|
10379
|
+
name: "memory",
|
|
10380
|
+
description: "Show recent golden-task memory.",
|
|
10381
|
+
category: "built-in",
|
|
10382
|
+
async handler(args, ctx) {
|
|
10383
|
+
const records = [...await readGoldenTaskMemory(ctx.cwd)].sort((a, b) => b.recordedAt.localeCompare(a.recordedAt));
|
|
10384
|
+
const fullId = flagValue(args, "--full");
|
|
10385
|
+
if (fullId) {
|
|
10386
|
+
const record = records.find((candidate) => candidate.signature === fullId);
|
|
10387
|
+
ctx.output.write(record ? `${JSON.stringify(record, null, 2)}
|
|
10388
|
+
` : `Golden task not found: ${fullId}
|
|
10389
|
+
`);
|
|
10390
|
+
return;
|
|
10391
|
+
}
|
|
10392
|
+
const limit = parseLimit(flagValue(args, "--limit"));
|
|
10393
|
+
if (records.length === 0) {
|
|
10394
|
+
ctx.output.write("No golden task memory found.\n");
|
|
10395
|
+
return;
|
|
10396
|
+
}
|
|
10397
|
+
ctx.output.write("Recent golden tasks:\n");
|
|
10398
|
+
for (const record of records.slice(0, limit)) {
|
|
10399
|
+
const title = record.task?.title ?? "(untitled)";
|
|
10400
|
+
ctx.output.write(`${record.recordedAt.slice(0, 16)} ${record.outcome.padEnd(6)} ${record.signature} ${title}
|
|
10401
|
+
`);
|
|
10402
|
+
}
|
|
10403
|
+
}
|
|
10404
|
+
};
|
|
10405
|
+
function flagValue(args, flag) {
|
|
10406
|
+
const index = args.indexOf(flag);
|
|
10407
|
+
const value = index >= 0 ? args[index + 1] : void 0;
|
|
10408
|
+
return value && !value.startsWith("--") ? value : void 0;
|
|
10409
|
+
}
|
|
10410
|
+
function parseLimit(raw) {
|
|
10411
|
+
const parsed = raw ? Number(raw) : DEFAULT_LIMIT;
|
|
10412
|
+
return Number.isInteger(parsed) && parsed > 0 ? parsed : DEFAULT_LIMIT;
|
|
10413
|
+
}
|
|
10414
|
+
registerCommand(memoryCommand);
|
|
10415
|
+
|
|
10416
|
+
// src/commands/builtin/skills.ts
|
|
10417
|
+
var skillsCommand = {
|
|
10418
|
+
name: "skills",
|
|
10419
|
+
description: "Show skill packs matched for this workspace.",
|
|
10420
|
+
category: "built-in",
|
|
10421
|
+
handler(args, ctx) {
|
|
10422
|
+
const taskHint = args.join(" ");
|
|
10423
|
+
const packs = loadPromptSkillPacks(ctx.cwd, void 0, taskHint);
|
|
10424
|
+
ctx.output.write(`${formatSkillPackSummary(packs)}
|
|
10425
|
+
`);
|
|
10426
|
+
}
|
|
10427
|
+
};
|
|
10428
|
+
registerCommand(skillsCommand);
|
|
10429
|
+
|
|
10430
|
+
// src/commands/builtin/verify.ts
|
|
10431
|
+
var verifyCommand = {
|
|
10432
|
+
name: "verify",
|
|
10433
|
+
description: "Run Tanya's deterministic final-state verifier for the current workspace.",
|
|
10434
|
+
category: "built-in",
|
|
10435
|
+
async handler(_args, ctx) {
|
|
10436
|
+
const beforeGitSnapshot = await captureGitSnapshot(ctx.cwd);
|
|
10437
|
+
const manifest = await buildFinalManifest({
|
|
10438
|
+
workspace: ctx.cwd,
|
|
10439
|
+
beforeGitSnapshot,
|
|
10440
|
+
changed: [],
|
|
10441
|
+
verificationLines: [],
|
|
10442
|
+
toolErrorCount: 0,
|
|
10443
|
+
readArtifactPaths: [],
|
|
10444
|
+
readContextPaths: [],
|
|
10445
|
+
createdArtifactPaths: [],
|
|
10446
|
+
blockers: [],
|
|
10447
|
+
prompt: "Ad-hoc /verify command"
|
|
10448
|
+
});
|
|
10449
|
+
const report = ensureCodingReport("", manifest);
|
|
10450
|
+
ctx.output.write(`${report.trim() || "No verifier evidence available for this workspace."}
|
|
10451
|
+
`);
|
|
10452
|
+
}
|
|
10453
|
+
};
|
|
10454
|
+
registerCommand(verifyCommand);
|
|
10455
|
+
|
|
10456
|
+
// src/commands/project.ts
|
|
10457
|
+
import { existsSync as existsSync16, readdirSync as readdirSync6 } from "fs";
|
|
10458
|
+
import { basename as basename4, extname as extname2, join as join18, relative as relative10 } from "path";
|
|
10459
|
+
import { pathToFileURL } from "url";
|
|
10460
|
+
import { tsImport } from "tsx/esm/api";
|
|
10461
|
+
var supportedExtensions = /* @__PURE__ */ new Set([".js", ".ts", ".sh"]);
|
|
10462
|
+
var loadedWorkspace = null;
|
|
10463
|
+
async function loadProjectCommands(workspace) {
|
|
10464
|
+
if (loadedWorkspace === workspace) return;
|
|
10465
|
+
loadedWorkspace = workspace;
|
|
10466
|
+
removeCommandsByCategory("project");
|
|
10467
|
+
const commandsDir = join18(workspace, ".tania", "commands");
|
|
10468
|
+
if (!existsSync16(commandsDir)) return;
|
|
10469
|
+
let files = [];
|
|
10470
|
+
try {
|
|
10471
|
+
files = readdirSync6(commandsDir).filter((file) => supportedExtensions.has(extname2(file))).sort();
|
|
10472
|
+
} catch (error) {
|
|
10473
|
+
warnProjectCommand(commandsDir, error);
|
|
10474
|
+
return;
|
|
10475
|
+
}
|
|
10476
|
+
for (const file of files) {
|
|
10477
|
+
const path = join18(commandsDir, file);
|
|
10478
|
+
try {
|
|
10479
|
+
const extension = extname2(file);
|
|
10480
|
+
if (extension === ".sh") {
|
|
10481
|
+
registerShellCommand(workspace, path);
|
|
10482
|
+
} else {
|
|
10483
|
+
await registerModuleCommand(path);
|
|
10484
|
+
}
|
|
10485
|
+
} catch (error) {
|
|
10486
|
+
warnProjectCommand(path, error);
|
|
10487
|
+
}
|
|
10488
|
+
}
|
|
10489
|
+
}
|
|
10490
|
+
function registerShellCommand(workspace, path) {
|
|
10491
|
+
const slug = basename4(path, extname2(path));
|
|
10492
|
+
const relativePath = relative10(workspace, path).replace(/\\/g, "/");
|
|
10493
|
+
registerCommand({
|
|
10494
|
+
name: `project:${slug}`,
|
|
10495
|
+
description: `Run ${relativePath}.`,
|
|
10496
|
+
category: "project",
|
|
10497
|
+
async handler(args, ctx) {
|
|
10498
|
+
const script = ["zsh", quoteShell(relativePath), ...args.map(quoteShell)].join(" ");
|
|
10499
|
+
const result = await runShellTool.run({ script }, { workspace: ctx.cwd });
|
|
10500
|
+
const output = typeof result.output === "string" ? result.output : "";
|
|
10501
|
+
if (output) ctx.output.write(`${output}${output.endsWith("\n") ? "" : "\n"}`);
|
|
10502
|
+
if (!result.ok) ctx.output.write(`${result.error ?? result.summary}
|
|
10503
|
+
`);
|
|
10504
|
+
}
|
|
10505
|
+
});
|
|
10506
|
+
}
|
|
10507
|
+
async function registerModuleCommand(path) {
|
|
10508
|
+
const href = pathToFileURL(path).href;
|
|
10509
|
+
const imported = extname2(path) === ".ts" ? await tsImport(href, import.meta.url) : await import(href);
|
|
10510
|
+
const command = unwrapDefaultCommand(imported);
|
|
10511
|
+
if (!isCommandDefinition(command)) {
|
|
10512
|
+
throw new Error("default export is not a CommandDefinition");
|
|
10513
|
+
}
|
|
10514
|
+
registerCommand({
|
|
10515
|
+
...command,
|
|
10516
|
+
name: command.name.startsWith("project:") ? command.name : `project:${command.name}`,
|
|
10517
|
+
category: "project"
|
|
10518
|
+
});
|
|
10519
|
+
}
|
|
10520
|
+
function unwrapDefaultCommand(imported) {
|
|
10521
|
+
if (isCommandDefinition(imported.default)) return imported.default;
|
|
10522
|
+
const nested = imported.default;
|
|
10523
|
+
if (nested && typeof nested === "object" && "default" in nested) {
|
|
10524
|
+
return nested.default;
|
|
10525
|
+
}
|
|
10526
|
+
return imported.default;
|
|
10527
|
+
}
|
|
10528
|
+
function isCommandDefinition(value) {
|
|
10529
|
+
return Boolean(
|
|
10530
|
+
value && typeof value === "object" && typeof value.name === "string" && typeof value.description === "string" && typeof value.handler === "function"
|
|
10531
|
+
);
|
|
10532
|
+
}
|
|
10533
|
+
function quoteShell(value) {
|
|
10534
|
+
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
10535
|
+
}
|
|
10536
|
+
function warnProjectCommand(path, error) {
|
|
10537
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
10538
|
+
console.warn(`[tanya] Skipping project command ${path}: ${message}`);
|
|
10539
|
+
}
|
|
10540
|
+
|
|
10541
|
+
// src/commands/index.ts
|
|
10542
|
+
function parseCommandLine(line) {
|
|
10543
|
+
const trimmed = line.trim();
|
|
10544
|
+
if (!trimmed.startsWith("/")) return null;
|
|
10545
|
+
const body = trimmed.slice(1).trim();
|
|
10546
|
+
if (!body) return null;
|
|
10547
|
+
const firstWhitespace = body.search(/\s/);
|
|
10548
|
+
const rawName = firstWhitespace === -1 ? body : body.slice(0, firstWhitespace);
|
|
10549
|
+
const rawArgs = firstWhitespace === -1 ? "" : body.slice(firstWhitespace).trim();
|
|
10550
|
+
const name = normalizeCommandName(rawName);
|
|
10551
|
+
if (!/^[a-z0-9:_-]+$/.test(name)) return null;
|
|
10552
|
+
return { name, args: splitCommandArgs(rawArgs) };
|
|
10553
|
+
}
|
|
10554
|
+
function commandNameFromLine(line) {
|
|
10555
|
+
return parseCommandLine(line)?.name ?? null;
|
|
10556
|
+
}
|
|
10557
|
+
async function runCommand2(line, ctx) {
|
|
10558
|
+
const parsed = parseCommandLine(line);
|
|
10559
|
+
if (!parsed) return false;
|
|
10560
|
+
await loadProjectCommands(ctx.cwd);
|
|
10561
|
+
const command = getCommand(parsed.name);
|
|
10562
|
+
if (!command) return false;
|
|
10563
|
+
if (!await commandIsAvailable(command, ctx)) return false;
|
|
10564
|
+
await ctx.sink({
|
|
10565
|
+
type: "command_invoked",
|
|
10566
|
+
name: command.name,
|
|
10567
|
+
args: parsed.args,
|
|
10568
|
+
...ctx.runId ? { runId: ctx.runId } : {}
|
|
10569
|
+
});
|
|
10570
|
+
await command.handler(parsed.args, ctx);
|
|
10571
|
+
return true;
|
|
10572
|
+
}
|
|
10573
|
+
function splitCommandArgs(input) {
|
|
10574
|
+
const args = [];
|
|
10575
|
+
let current = "";
|
|
10576
|
+
let quote = null;
|
|
10577
|
+
let escaping = false;
|
|
10578
|
+
for (const char of input) {
|
|
10579
|
+
if (escaping) {
|
|
10580
|
+
current += char;
|
|
10581
|
+
escaping = false;
|
|
10582
|
+
continue;
|
|
10583
|
+
}
|
|
10584
|
+
if (char === "\\") {
|
|
10585
|
+
escaping = true;
|
|
10586
|
+
continue;
|
|
10587
|
+
}
|
|
10588
|
+
if (quote) {
|
|
10589
|
+
if (char === quote) {
|
|
10590
|
+
quote = null;
|
|
10591
|
+
} else {
|
|
10592
|
+
current += char;
|
|
10593
|
+
}
|
|
10594
|
+
continue;
|
|
10595
|
+
}
|
|
10596
|
+
if (char === '"' || char === "'") {
|
|
10597
|
+
quote = char;
|
|
10598
|
+
continue;
|
|
10599
|
+
}
|
|
10600
|
+
if (/\s/.test(char)) {
|
|
10601
|
+
if (current) {
|
|
10602
|
+
args.push(current);
|
|
10603
|
+
current = "";
|
|
10604
|
+
}
|
|
10605
|
+
continue;
|
|
10606
|
+
}
|
|
10607
|
+
current += char;
|
|
10608
|
+
}
|
|
10609
|
+
if (escaping) current += "\\";
|
|
10610
|
+
if (current) args.push(current);
|
|
10611
|
+
return args;
|
|
10612
|
+
}
|
|
10613
|
+
|
|
10614
|
+
// src/agent/chat.ts
|
|
10615
|
+
async function dispatchInteractiveCommand(prompt, ctx) {
|
|
10616
|
+
if (!prompt.startsWith("/")) return false;
|
|
10617
|
+
const handled = await runCommand2(prompt, ctx);
|
|
10618
|
+
if (handled) return true;
|
|
10619
|
+
const commandName = commandNameFromLine(prompt);
|
|
10620
|
+
ctx.output.write(`unknown command: /${commandName ?? ""}; try /help
|
|
10621
|
+
`);
|
|
10622
|
+
return true;
|
|
10623
|
+
}
|
|
9544
10624
|
async function startInteractiveChat(inputOptions) {
|
|
10625
|
+
const input = inputOptions.input ?? processInput;
|
|
10626
|
+
const output = inputOptions.output ?? processOutput;
|
|
9545
10627
|
const rl = createInterface({ input, output });
|
|
9546
10628
|
const history = [];
|
|
9547
10629
|
let activeAbortController = null;
|
|
10630
|
+
await loadProjectCommands(inputOptions.cwd);
|
|
9548
10631
|
output.write(`Tanya live chat (${inputOptions.provider.id}:${inputOptions.provider.model}). Type /exit to quit.
|
|
9549
10632
|
`);
|
|
9550
10633
|
const handleSigint = () => {
|
|
@@ -9562,17 +10645,34 @@ async function startInteractiveChat(inputOptions) {
|
|
|
9562
10645
|
const prompt = (await rl.question("\nYou: ")).trim();
|
|
9563
10646
|
if (!prompt) continue;
|
|
9564
10647
|
if (prompt === "/exit" || prompt === "/quit") break;
|
|
9565
|
-
|
|
9566
|
-
activeAbortController = abortController;
|
|
9567
|
-
const { message } = await runAgent({
|
|
10648
|
+
if (await dispatchInteractiveCommand(prompt, {
|
|
9568
10649
|
provider: inputOptions.provider,
|
|
9569
|
-
prompt,
|
|
9570
10650
|
cwd: inputOptions.cwd,
|
|
9571
10651
|
sink: inputOptions.sink,
|
|
10652
|
+
output,
|
|
9572
10653
|
history,
|
|
9573
|
-
|
|
9574
|
-
|
|
9575
|
-
|
|
10654
|
+
clearHistory: () => {
|
|
10655
|
+
history.length = 0;
|
|
10656
|
+
}
|
|
10657
|
+
})) {
|
|
10658
|
+
continue;
|
|
10659
|
+
}
|
|
10660
|
+
const abortController = new AbortController();
|
|
10661
|
+
activeAbortController = abortController;
|
|
10662
|
+
let message;
|
|
10663
|
+
try {
|
|
10664
|
+
const result = await runAgent({
|
|
10665
|
+
provider: inputOptions.provider,
|
|
10666
|
+
prompt,
|
|
10667
|
+
cwd: inputOptions.cwd,
|
|
10668
|
+
sink: inputOptions.sink,
|
|
10669
|
+
history,
|
|
10670
|
+
signal: abortController.signal
|
|
10671
|
+
});
|
|
10672
|
+
message = result.message;
|
|
10673
|
+
} finally {
|
|
10674
|
+
activeAbortController = null;
|
|
10675
|
+
}
|
|
9576
10676
|
history.push({ role: "user", content: prompt });
|
|
9577
10677
|
history.push({ role: "assistant", content: message });
|
|
9578
10678
|
}
|
|
@@ -9628,6 +10728,21 @@ ${toolGlyph} ${event.tool}
|
|
|
9628
10728
|
break;
|
|
9629
10729
|
case "tool_cancelled":
|
|
9630
10730
|
stream.write(` cancelled: ${event.tool ?? event.toolCallId}
|
|
10731
|
+
`);
|
|
10732
|
+
break;
|
|
10733
|
+
case "command_invoked":
|
|
10734
|
+
break;
|
|
10735
|
+
case "tool_call_parse_warning":
|
|
10736
|
+
stream.write(` warning: malformed tool call (${event.reason})
|
|
10737
|
+
`);
|
|
10738
|
+
break;
|
|
10739
|
+
case "schema_flatten_warning":
|
|
10740
|
+
stream.write(` warning: flattened schema${event.tool ? ` for ${event.tool}` : ""} (${event.reason})
|
|
10741
|
+
`);
|
|
10742
|
+
break;
|
|
10743
|
+
case "provider_throttle":
|
|
10744
|
+
stream.write(`
|
|
10745
|
+
Provider ${event.provider} throttled; waiting ${Math.ceil(event.waitMs / 1e3)}s before retry ${event.attempt}.
|
|
9631
10746
|
`);
|
|
9632
10747
|
break;
|
|
9633
10748
|
case "final":
|
|
@@ -9651,7 +10766,7 @@ ${event.detail}` : ""}
|
|
|
9651
10766
|
|
|
9652
10767
|
// src/tools/adRenderTools.ts
|
|
9653
10768
|
import { mkdir as mkdir11, readFile as readFile10, readdir as readdir6, rm as rm3, writeFile as writeFile9 } from "fs/promises";
|
|
9654
|
-
import { dirname as dirname13, isAbsolute as isAbsolute4, join as
|
|
10769
|
+
import { dirname as dirname13, isAbsolute as isAbsolute4, join as join19 } from "path";
|
|
9655
10770
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
9656
10771
|
import sharp3 from "sharp";
|
|
9657
10772
|
function findExecutable2(name, explicit) {
|
|
@@ -9745,17 +10860,17 @@ async function loadStaticFrame(src, workspace) {
|
|
|
9745
10860
|
}
|
|
9746
10861
|
async function extractVideoFrames(params) {
|
|
9747
10862
|
await mkdir11(params.outDir, { recursive: true });
|
|
9748
|
-
const
|
|
10863
|
+
const input = resolveAssetPath(params.src, params.workspace);
|
|
9749
10864
|
run2(params.ffmpegPath, [
|
|
9750
10865
|
"-y",
|
|
9751
10866
|
"-i",
|
|
9752
|
-
|
|
10867
|
+
input,
|
|
9753
10868
|
"-vf",
|
|
9754
10869
|
`fps=${params.fps}`,
|
|
9755
|
-
|
|
10870
|
+
join19(params.outDir, "frame-%05d.png")
|
|
9756
10871
|
], params.workspace);
|
|
9757
10872
|
const files = (await readdir6(params.outDir)).filter((file) => file.endsWith(".png")).sort();
|
|
9758
|
-
return Promise.all(files.map((file) => readFile10(
|
|
10873
|
+
return Promise.all(files.map((file) => readFile10(join19(params.outDir, file))));
|
|
9759
10874
|
}
|
|
9760
10875
|
async function buildMediaFrames(spec, workspace, tmpDir, ffmpegPath, warnings) {
|
|
9761
10876
|
const media = /* @__PURE__ */ new Map();
|
|
@@ -9765,7 +10880,7 @@ async function buildMediaFrames(spec, workspace, tmpDir, ffmpegPath, warnings) {
|
|
|
9765
10880
|
const frames = await extractVideoFrames({
|
|
9766
10881
|
src: asset.src,
|
|
9767
10882
|
workspace,
|
|
9768
|
-
outDir:
|
|
10883
|
+
outDir: join19(tmpDir, "media", asset.id),
|
|
9769
10884
|
fps: spec.canvas.fps,
|
|
9770
10885
|
ffmpegPath
|
|
9771
10886
|
});
|
|
@@ -9861,13 +10976,13 @@ async function shadowLayer(width, height, radius) {
|
|
|
9861
10976
|
}
|
|
9862
10977
|
}).composite([{ input: mask, blend: "dest-in" }]).blur(18).extend({ top: 24, bottom: 24, left: 24, right: 24, background: { r: 0, g: 0, b: 0, alpha: 0 } }).png().toBuffer();
|
|
9863
10978
|
}
|
|
9864
|
-
async function scaleOverlay(
|
|
9865
|
-
if (Math.abs(scale - 1) < 1e-3) return { input
|
|
9866
|
-
const meta = await sharp3(
|
|
10979
|
+
async function scaleOverlay(input, scale) {
|
|
10980
|
+
if (Math.abs(scale - 1) < 1e-3) return { input, dx: 0, dy: 0 };
|
|
10981
|
+
const meta = await sharp3(input).metadata();
|
|
9867
10982
|
const width = Math.max(1, Math.round((meta.width ?? 1) * scale));
|
|
9868
10983
|
const height = Math.max(1, Math.round((meta.height ?? 1) * scale));
|
|
9869
10984
|
return {
|
|
9870
|
-
input: await sharp3(
|
|
10985
|
+
input: await sharp3(input).resize(width, height).png().toBuffer(),
|
|
9871
10986
|
dx: Math.round(((meta.width ?? width) - width) / 2),
|
|
9872
10987
|
dy: Math.round(((meta.height ?? height) - height) / 2)
|
|
9873
10988
|
};
|
|
@@ -9963,12 +11078,12 @@ async function renderFullAd(options, workspace) {
|
|
|
9963
11078
|
const inputPath = isAbsolute4(options.input) ? options.input : resolveInsideWorkspace(workspace, options.input);
|
|
9964
11079
|
const spec = parseSpec(JSON.parse(await readFile10(inputPath, "utf8")));
|
|
9965
11080
|
const outputDir = resolveInsideWorkspace(workspace, options.outputDir ?? "tanya-video-ads");
|
|
9966
|
-
const
|
|
11081
|
+
const basename5 = (options.basename ?? `full-ad-${Date.now()}`).replace(/[^a-zA-Z0-9._-]+/g, "-");
|
|
9967
11082
|
const formats = options.formats?.length ? options.formats : ["mp4", "poster"];
|
|
9968
11083
|
const ffmpegPath = findExecutable2("ffmpeg", options.ffmpegPath ?? envValue({}, "TANYA_FFMPEG_PATH"));
|
|
9969
11084
|
const tmpRoot = resolveInsideWorkspace(workspace, ".tania/tmp");
|
|
9970
|
-
const tmpDir = await fsMkdtemp(
|
|
9971
|
-
const frameDir =
|
|
11085
|
+
const tmpDir = await fsMkdtemp(join19(tmpRoot, "tanya-render-ad-"));
|
|
11086
|
+
const frameDir = join19(tmpDir, "frames");
|
|
9972
11087
|
const warnings = [];
|
|
9973
11088
|
await mkdir11(frameDir, { recursive: true });
|
|
9974
11089
|
await mkdir11(outputDir, { recursive: true });
|
|
@@ -9980,7 +11095,7 @@ async function renderFullAd(options, workspace) {
|
|
|
9980
11095
|
for (let i = 0; i < frameCount; i += 1) {
|
|
9981
11096
|
const frame = await renderFrame(spec, scene, mediaFrames, i);
|
|
9982
11097
|
await writeFile9(
|
|
9983
|
-
|
|
11098
|
+
join19(frameDir, `frame_${String(frameIndex).padStart(6, "0")}.jpg`),
|
|
9984
11099
|
await sharp3(frame).jpeg({ quality: 84, mozjpeg: true }).toBuffer()
|
|
9985
11100
|
);
|
|
9986
11101
|
frameIndex += 1;
|
|
@@ -9995,18 +11110,18 @@ async function renderFullAd(options, workspace) {
|
|
|
9995
11110
|
renderSpec: spec
|
|
9996
11111
|
};
|
|
9997
11112
|
if (formats.includes("poster")) {
|
|
9998
|
-
const posterPath =
|
|
9999
|
-
await sharp3(await readFile10(
|
|
11113
|
+
const posterPath = join19(outputDir, `${basename5}-poster.png`);
|
|
11114
|
+
await sharp3(await readFile10(join19(frameDir, "frame_000000.jpg"))).png().toFile(posterPath);
|
|
10000
11115
|
result.posterPath = posterPath;
|
|
10001
11116
|
}
|
|
10002
11117
|
if (formats.includes("mp4")) {
|
|
10003
|
-
const mp4Path =
|
|
11118
|
+
const mp4Path = join19(outputDir, `${basename5}.mp4`);
|
|
10004
11119
|
run2(ffmpegPath, [
|
|
10005
11120
|
"-y",
|
|
10006
11121
|
"-framerate",
|
|
10007
11122
|
String(spec.canvas.fps),
|
|
10008
11123
|
"-i",
|
|
10009
|
-
|
|
11124
|
+
join19(frameDir, "frame_%06d.jpg"),
|
|
10010
11125
|
"-c:v",
|
|
10011
11126
|
"libx264",
|
|
10012
11127
|
"-pix_fmt",
|
|
@@ -10245,7 +11360,7 @@ var BUILT_IN_GOLDEN_TASK_PROFILES = [
|
|
|
10245
11360
|
import { execFile as execFile3 } from "child_process";
|
|
10246
11361
|
import { mkdtemp, mkdir as mkdir12, writeFile as writeFile10 } from "fs/promises";
|
|
10247
11362
|
import { tmpdir } from "os";
|
|
10248
|
-
import { join as
|
|
11363
|
+
import { join as join20 } from "path";
|
|
10249
11364
|
import { promisify as promisify3 } from "util";
|
|
10250
11365
|
function toolCall(id, name, args) {
|
|
10251
11366
|
return {
|
|
@@ -10263,15 +11378,15 @@ function scriptedProvider(id, turns) {
|
|
|
10263
11378
|
id: `golden:${id}`,
|
|
10264
11379
|
model: "scripted",
|
|
10265
11380
|
requests,
|
|
10266
|
-
async *streamChat(
|
|
10267
|
-
requests.push({ ...
|
|
11381
|
+
async *streamChat(input) {
|
|
11382
|
+
requests.push({ ...input, messages: [...input.messages] });
|
|
10268
11383
|
yield turns[Math.min(requests.length - 1, turns.length - 1)] ?? { content: "" };
|
|
10269
11384
|
}
|
|
10270
11385
|
};
|
|
10271
11386
|
}
|
|
10272
11387
|
var execFileAsync3 = promisify3(execFile3);
|
|
10273
11388
|
async function createBaseWorkspace(profileId) {
|
|
10274
|
-
return mkdtemp(
|
|
11389
|
+
return mkdtemp(join20(tmpdir(), `tanya-golden-${profileId.replace(/[^a-z0-9]+/gi, "-")}-`));
|
|
10275
11390
|
}
|
|
10276
11391
|
var GENERIC_BENCHMARK_SPECS = {
|
|
10277
11392
|
"tanya.low.search-replace": {
|
|
@@ -10423,8 +11538,8 @@ async function genericBenchmarkFixture(profile) {
|
|
|
10423
11538
|
if (!spec) throw new Error(`No generic benchmark spec for ${profile.id}`);
|
|
10424
11539
|
const workspace = await createBaseWorkspace(profile.id);
|
|
10425
11540
|
const marker = `${profile.id.split(".").pop() ?? "benchmark"}-ready`;
|
|
10426
|
-
await mkdir12(
|
|
10427
|
-
await writeFile10(
|
|
11541
|
+
await mkdir12(join20(workspace, "scripts"), { recursive: true });
|
|
11542
|
+
await writeFile10(join20(workspace, "scripts/check.js"), [
|
|
10428
11543
|
"const fs = require('fs');",
|
|
10429
11544
|
`const targets = ${JSON.stringify(spec.targetFiles.map((file) => ({ path: file.path, marker })))};`,
|
|
10430
11545
|
"for (const target of targets) {",
|
|
@@ -10440,37 +11555,37 @@ async function genericBenchmarkFixture(profile) {
|
|
|
10440
11555
|
if (spec.useSearchReplace) {
|
|
10441
11556
|
for (const file of spec.targetFiles) {
|
|
10442
11557
|
const dir = file.path.split("/").slice(0, -1).join("/");
|
|
10443
|
-
if (dir) await mkdir12(
|
|
10444
|
-
await writeFile10(
|
|
11558
|
+
if (dir) await mkdir12(join20(workspace, dir), { recursive: true });
|
|
11559
|
+
await writeFile10(join20(workspace, file.path), file.content.replace(marker, "PENDING_MARKER"));
|
|
10445
11560
|
}
|
|
10446
11561
|
}
|
|
10447
11562
|
for (const file of spec.preFiles ?? []) {
|
|
10448
11563
|
const dir = file.path.split("/").slice(0, -1).join("/");
|
|
10449
|
-
if (dir) await mkdir12(
|
|
10450
|
-
await writeFile10(
|
|
11564
|
+
if (dir) await mkdir12(join20(workspace, dir), { recursive: true });
|
|
11565
|
+
await writeFile10(join20(workspace, file.path), file.content);
|
|
10451
11566
|
}
|
|
10452
11567
|
if (spec.useArtifact) {
|
|
10453
|
-
await mkdir12(
|
|
10454
|
-
await writeFile10(
|
|
11568
|
+
await mkdir12(join20(workspace, ".tania/artifacts/generic"), { recursive: true });
|
|
11569
|
+
await writeFile10(join20(workspace, ".tania/artifacts/generic/Pattern.md"), `# Pattern
|
|
10455
11570
|
|
|
10456
11571
|
Use ${marker} in the adapted output.
|
|
10457
11572
|
`);
|
|
10458
11573
|
}
|
|
10459
11574
|
if (spec.useContext) {
|
|
10460
|
-
await mkdir12(
|
|
10461
|
-
await writeFile10(
|
|
11575
|
+
await mkdir12(join20(workspace, ".tania/context"), { recursive: true });
|
|
11576
|
+
await writeFile10(join20(workspace, ".tania/context/task.md"), `# Task Context
|
|
10462
11577
|
|
|
10463
11578
|
Use ${marker}.
|
|
10464
11579
|
`);
|
|
10465
11580
|
}
|
|
10466
11581
|
if (spec.dirtyWorktree) {
|
|
10467
|
-
await writeFile10(
|
|
11582
|
+
await writeFile10(join20(workspace, "unrelated.txt"), "baseline\n");
|
|
10468
11583
|
await execFileAsync3("git", ["init"], { cwd: workspace });
|
|
10469
11584
|
await execFileAsync3("git", ["config", "user.email", "tanya@example.test"], { cwd: workspace });
|
|
10470
11585
|
await execFileAsync3("git", ["config", "user.name", "Tanya Benchmark"], { cwd: workspace });
|
|
10471
11586
|
await execFileAsync3("git", ["add", "unrelated.txt"], { cwd: workspace });
|
|
10472
11587
|
await execFileAsync3("git", ["commit", "-m", "baseline"], { cwd: workspace });
|
|
10473
|
-
await writeFile10(
|
|
11588
|
+
await writeFile10(join20(workspace, "unrelated.txt"), "pre-existing dirty change\n");
|
|
10474
11589
|
}
|
|
10475
11590
|
const toolCalls = [];
|
|
10476
11591
|
if (spec.useArtifact) toolCalls.push(toolCall("read-artifact", "read_file", { path: ".tania/artifacts/generic/Pattern.md" }));
|
|
@@ -10516,8 +11631,8 @@ Use ${marker}.
|
|
|
10516
11631
|
async function streamingLongToolFixture(profile) {
|
|
10517
11632
|
const workspace = await createBaseWorkspace(profile.id);
|
|
10518
11633
|
const script = 'for i in 1 2 3 4 5 6; do printf "stream-$i\\n"; sleep 2; done; node scripts/check.js';
|
|
10519
|
-
await mkdir12(
|
|
10520
|
-
await writeFile10(
|
|
11634
|
+
await mkdir12(join20(workspace, "scripts"), { recursive: true });
|
|
11635
|
+
await writeFile10(join20(workspace, "scripts/check.js"), [
|
|
10521
11636
|
"const fs = require('fs');",
|
|
10522
11637
|
"const text = fs.readFileSync('src/streamingLongTool.ts', 'utf8');",
|
|
10523
11638
|
"if (!text.includes('streaming-long-tool-ready')) process.exit(1);",
|
|
@@ -10558,13 +11673,13 @@ async function streamingLongToolFixture(profile) {
|
|
|
10558
11673
|
}
|
|
10559
11674
|
async function androidSplashFixture(profile) {
|
|
10560
11675
|
const workspace = await createBaseWorkspace(profile.id);
|
|
10561
|
-
await mkdir12(
|
|
10562
|
-
await mkdir12(
|
|
10563
|
-
await mkdir12(
|
|
10564
|
-
await mkdir12(
|
|
10565
|
-
await writeFile10(
|
|
10566
|
-
await writeFile10(
|
|
10567
|
-
await writeFile10(
|
|
11676
|
+
await mkdir12(join20(workspace, ".tania/artifacts/android"), { recursive: true });
|
|
11677
|
+
await mkdir12(join20(workspace, "app/src/main/java/com/example/app/ui/splash"), { recursive: true });
|
|
11678
|
+
await mkdir12(join20(workspace, "app/src/main/res/values"), { recursive: true });
|
|
11679
|
+
await mkdir12(join20(workspace, "app/src/main/res/drawable"), { recursive: true });
|
|
11680
|
+
await writeFile10(join20(workspace, ".tania/artifacts/android/SplashScreenPattern.kt"), "fun SplashPattern() {}\n");
|
|
11681
|
+
await writeFile10(join20(workspace, "gradlew"), '#!/bin/sh\ncase "$*" in\n *ktlintCheck*) echo ktlint ok ;;\n *) echo BUILD SUCCESSFUL ;;\nesac\n');
|
|
11682
|
+
await writeFile10(join20(workspace, "app/src/main/res/drawable/ic_splash_logo.png"), "png\n");
|
|
10568
11683
|
const provider = scriptedProvider(profile.id, [
|
|
10569
11684
|
{
|
|
10570
11685
|
content: "Create Android splash resources from the artifact.",
|
|
@@ -10621,10 +11736,10 @@ async function androidSplashFixture(profile) {
|
|
|
10621
11736
|
}
|
|
10622
11737
|
async function iosFoundationFixture(profile) {
|
|
10623
11738
|
const workspace = await createBaseWorkspace(profile.id);
|
|
10624
|
-
await mkdir12(
|
|
10625
|
-
await writeFile10(
|
|
10626
|
-
await writeFile10(
|
|
10627
|
-
await writeFile10(
|
|
11739
|
+
await mkdir12(join20(workspace, ".tania/artifacts/ios"), { recursive: true });
|
|
11740
|
+
await writeFile10(join20(workspace, ".tania/artifacts/ios/ThemeSystem.swift"), "import SwiftUI\n");
|
|
11741
|
+
await writeFile10(join20(workspace, ".tania/artifacts/ios/SwiftDataSetup.swift"), "import SwiftData\n");
|
|
11742
|
+
await writeFile10(join20(workspace, ".tania/artifacts/ios/NavigationSetup.swift"), "import SwiftUI\n");
|
|
10628
11743
|
const provider = scriptedProvider(profile.id, [
|
|
10629
11744
|
{
|
|
10630
11745
|
content: "Create iOS foundation from theme, SwiftData, and navigation artifacts.",
|
|
@@ -10695,7 +11810,7 @@ async function iosFoundationFixture(profile) {
|
|
|
10695
11810
|
}
|
|
10696
11811
|
async function appleAppIconFixture(profile) {
|
|
10697
11812
|
const workspace = await createBaseWorkspace(profile.id);
|
|
10698
|
-
await mkdir12(
|
|
11813
|
+
await mkdir12(join20(workspace, "CosaNostra/Assets.xcassets"), { recursive: true });
|
|
10699
11814
|
const provider = scriptedProvider(profile.id, [
|
|
10700
11815
|
{
|
|
10701
11816
|
content: "Generate Apple app icon set with iOS and macOS slots.",
|
|
@@ -10738,14 +11853,14 @@ async function appleAppIconFixture(profile) {
|
|
|
10738
11853
|
}
|
|
10739
11854
|
async function androidFoundationFixture(profile) {
|
|
10740
11855
|
const workspace = await createBaseWorkspace(profile.id);
|
|
10741
|
-
await mkdir12(
|
|
10742
|
-
await mkdir12(
|
|
10743
|
-
await writeFile10(
|
|
10744
|
-
await writeFile10(
|
|
10745
|
-
await writeFile10(
|
|
10746
|
-
await writeFile10(
|
|
10747
|
-
await writeFile10(
|
|
10748
|
-
await writeFile10(
|
|
11856
|
+
await mkdir12(join20(workspace, ".tania/artifacts/android"), { recursive: true });
|
|
11857
|
+
await mkdir12(join20(workspace, "app"), { recursive: true });
|
|
11858
|
+
await writeFile10(join20(workspace, ".tania/artifacts/android/ThemeSystem.kt"), "package artifact\n");
|
|
11859
|
+
await writeFile10(join20(workspace, ".tania/artifacts/android/NavigationSetup.kt"), "package artifact\n");
|
|
11860
|
+
await writeFile10(join20(workspace, ".tania/artifacts/android/RoomSetup.kt"), "package artifact\n");
|
|
11861
|
+
await writeFile10(join20(workspace, "gradlew"), '#!/bin/sh\ncase "$*" in\n *ktlintCheck*) echo ktlint ok ;;\n *) echo BUILD SUCCESSFUL ;;\nesac\n');
|
|
11862
|
+
await writeFile10(join20(workspace, "build.gradle.kts"), 'plugins {\n id("com.android.application") version "8.7.2" apply false\n}\n');
|
|
11863
|
+
await writeFile10(join20(workspace, "app/build.gradle.kts"), [
|
|
10749
11864
|
"plugins {",
|
|
10750
11865
|
' id("com.android.application")',
|
|
10751
11866
|
' id("org.jetbrains.kotlin.android")',
|
|
@@ -10806,12 +11921,12 @@ async function androidFoundationFixture(profile) {
|
|
|
10806
11921
|
}
|
|
10807
11922
|
async function backendApiFoundationFixture(profile) {
|
|
10808
11923
|
const workspace = await createBaseWorkspace(profile.id);
|
|
10809
|
-
await mkdir12(
|
|
10810
|
-
await mkdir12(
|
|
10811
|
-
await mkdir12(
|
|
10812
|
-
await mkdir12(
|
|
10813
|
-
await writeFile10(
|
|
10814
|
-
await writeFile10(
|
|
11924
|
+
await mkdir12(join20(workspace, ".tania/artifacts/backend"), { recursive: true });
|
|
11925
|
+
await mkdir12(join20(workspace, "app/api/health"), { recursive: true });
|
|
11926
|
+
await mkdir12(join20(workspace, "lib"), { recursive: true });
|
|
11927
|
+
await mkdir12(join20(workspace, "prisma"), { recursive: true });
|
|
11928
|
+
await writeFile10(join20(workspace, ".tania/artifacts/backend/JwtAuthRoutes.ts"), "export function authRoutePattern() {}\n");
|
|
11929
|
+
await writeFile10(join20(workspace, "package.json"), JSON.stringify({ scripts: { typecheck: "tsc --noEmit", test: "node --test" }, dependencies: {} }, null, 2));
|
|
10815
11930
|
const provider = scriptedProvider(profile.id, [
|
|
10816
11931
|
{
|
|
10817
11932
|
content: "Create backend health/API foundation from the route artifact.",
|
|
@@ -11005,7 +12120,7 @@ async function runGoldenSuiteCommand(workspace, action, json = false, options =
|
|
|
11005
12120
|
|
|
11006
12121
|
// src/init/projectInit.ts
|
|
11007
12122
|
import { mkdir as mkdir13, readFile as readFile11, readdir as readdir7, stat as stat7, writeFile as writeFile11 } from "fs/promises";
|
|
11008
|
-
import { dirname as dirname14, join as
|
|
12123
|
+
import { dirname as dirname14, join as join21, resolve as resolve11 } from "path";
|
|
11009
12124
|
async function fileExists(path) {
|
|
11010
12125
|
try {
|
|
11011
12126
|
const entry = await stat7(path);
|
|
@@ -11015,7 +12130,7 @@ async function fileExists(path) {
|
|
|
11015
12130
|
}
|
|
11016
12131
|
}
|
|
11017
12132
|
async function readPackageJson(workspace) {
|
|
11018
|
-
const packagePath =
|
|
12133
|
+
const packagePath = join21(workspace, "package.json");
|
|
11019
12134
|
try {
|
|
11020
12135
|
return JSON.parse(await readFile11(packagePath, "utf8"));
|
|
11021
12136
|
} catch {
|
|
@@ -11043,10 +12158,10 @@ async function detectStack(workspace) {
|
|
|
11043
12158
|
const pkg = await readPackageJson(workspace);
|
|
11044
12159
|
const entries = await rootEntries(workspace);
|
|
11045
12160
|
const hasPackageJson = pkg !== null;
|
|
11046
|
-
const hasPrisma = await fileExists(
|
|
12161
|
+
const hasPrisma = await fileExists(join21(workspace, "prisma", "schema.prisma"));
|
|
11047
12162
|
const hasNextConfig = entries.some((entry) => /^next\.config\.(?:js|mjs|cjs|ts)$/.test(entry));
|
|
11048
|
-
const hasTsconfig = await fileExists(
|
|
11049
|
-
const hasGradlew = await fileExists(
|
|
12163
|
+
const hasTsconfig = await fileExists(join21(workspace, "tsconfig.json"));
|
|
12164
|
+
const hasGradlew = await fileExists(join21(workspace, "gradlew"));
|
|
11050
12165
|
const xcodeProjects = entries.filter((entry) => entry.endsWith(".xcodeproj"));
|
|
11051
12166
|
const projectTypes = [];
|
|
11052
12167
|
if (hasNextConfig || hasDependency(pkg, "next")) projectTypes.push("Next.js");
|
|
@@ -11097,7 +12212,7 @@ function buildInstructions(detection) {
|
|
|
11097
12212
|
}
|
|
11098
12213
|
async function initTanyaProject(cwd) {
|
|
11099
12214
|
const workspace = resolve11(cwd);
|
|
11100
|
-
const instructionsPath =
|
|
12215
|
+
const instructionsPath = join21(workspace, ".tania", "INSTRUCTIONS.md");
|
|
11101
12216
|
const detection = await detectStack(workspace);
|
|
11102
12217
|
await mkdir13(dirname14(instructionsPath), { recursive: true });
|
|
11103
12218
|
try {
|
|
@@ -11236,6 +12351,7 @@ var cliOptionDefinitions = [
|
|
|
11236
12351
|
{ flags: "--output-dir <path>", key: "output-dir", kind: "string", property: "outputDir", aliases: ["outputDir"] },
|
|
11237
12352
|
{ flags: "--plan", key: "plan", kind: "boolean", property: "plan" },
|
|
11238
12353
|
{ flags: "--profile <id>", key: "profile", kind: "string", property: "profile" },
|
|
12354
|
+
{ flags: "--provider <name>", key: "provider", kind: "string", property: "provider" },
|
|
11239
12355
|
{ flags: "--prompt-file <path>", key: "prompt-file", kind: "string", property: "promptFile" },
|
|
11240
12356
|
{ flags: "--repair-attempts <count>", key: "repair-attempts", kind: "string", property: "repairAttempts" },
|
|
11241
12357
|
{ flags: "--require-verification <command>", key: "require-verification", kind: "array", property: "requireVerification" },
|
|
@@ -11382,6 +12498,11 @@ function applyCliProfileFlag(args) {
|
|
|
11382
12498
|
if (!profile) return;
|
|
11383
12499
|
process.env.TANYA_PROFILE = profile;
|
|
11384
12500
|
}
|
|
12501
|
+
function applyCliProviderFlag(args) {
|
|
12502
|
+
const provider = flagString(args, "provider");
|
|
12503
|
+
if (!provider) return;
|
|
12504
|
+
process.env.TANYA_PROVIDER = provider;
|
|
12505
|
+
}
|
|
11385
12506
|
function buildRetryContext(manifest, attempt, extraBlockers = []) {
|
|
11386
12507
|
const lines = [
|
|
11387
12508
|
`RETRY CONTEXT (attempt ${attempt}): the previous run did not complete cleanly.`,
|
|
@@ -11403,14 +12524,6 @@ function buildRetryContext(manifest, attempt, extraBlockers = []) {
|
|
|
11403
12524
|
lines.push("", "Fix the blockers above and complete the task.");
|
|
11404
12525
|
return lines.join("\n");
|
|
11405
12526
|
}
|
|
11406
|
-
function estimateRunCost(model, promptTokens, completionTokens) {
|
|
11407
|
-
const isReasoner = model.includes("reasoner");
|
|
11408
|
-
const inputRate = isReasoner ? 0.55 : 0.27;
|
|
11409
|
-
const outputRate = isReasoner ? 2.19 : 1.1;
|
|
11410
|
-
const cost = promptTokens / 1e6 * inputRate + completionTokens / 1e6 * outputRate;
|
|
11411
|
-
if (cost < 1e-3) return "<$0.001";
|
|
11412
|
-
return `~$${cost.toFixed(3)}`;
|
|
11413
|
-
}
|
|
11414
12527
|
function usage() {
|
|
11415
12528
|
return `Tanya CLI
|
|
11416
12529
|
|
|
@@ -11455,7 +12568,7 @@ Usage:
|
|
|
11455
12568
|
tanya benchmark profiles List benchmark profiles
|
|
11456
12569
|
tanya benchmark run --all Run executable regression benchmarks
|
|
11457
12570
|
tanya benchmark validate Validate recent benchmark signatures
|
|
11458
|
-
tanya providers test
|
|
12571
|
+
tanya providers test --provider deepseek Test provider configuration (live only with TANYA_RUN_LIVE_PROVIDER_TESTS=1)
|
|
11459
12572
|
tanya doctor Check local setup
|
|
11460
12573
|
tanya patterns Show forbidden-pattern fire metrics for this workspace
|
|
11461
12574
|
|
|
@@ -11466,7 +12579,7 @@ Install locally during development:
|
|
|
11466
12579
|
}
|
|
11467
12580
|
function readPrompt(args) {
|
|
11468
12581
|
const promptFile = flagString(args, "prompt-file");
|
|
11469
|
-
if (promptFile) return
|
|
12582
|
+
if (promptFile) return readFileSync8(resolve12(promptFile), "utf8");
|
|
11470
12583
|
return args.positional.join(" ").trim();
|
|
11471
12584
|
}
|
|
11472
12585
|
async function buildRunContextForCli(args, cwd, prompt, obsidianVault) {
|
|
@@ -11474,7 +12587,7 @@ async function buildRunContextForCli(args, cwd, prompt, obsidianVault) {
|
|
|
11474
12587
|
const baseRunContext = contextFile ? loadRunContextFile(contextFile) : void 0;
|
|
11475
12588
|
const explicitArtifactsRoot = flagString(args, "artifacts-root");
|
|
11476
12589
|
const localArtifactsRoot = resolve12(cwd, "artifacts");
|
|
11477
|
-
const artifactsRoot = explicitArtifactsRoot ?? (
|
|
12590
|
+
const artifactsRoot = explicitArtifactsRoot ?? (existsSync17(localArtifactsRoot) ? localArtifactsRoot : void 0);
|
|
11478
12591
|
let runContext = materializeCliArtifacts({
|
|
11479
12592
|
cwd,
|
|
11480
12593
|
root: artifactsRoot,
|
|
@@ -11574,17 +12687,6 @@ ${heading}
|
|
|
11574
12687
|
}
|
|
11575
12688
|
return matched.join("\n\n---\n\n");
|
|
11576
12689
|
}
|
|
11577
|
-
function formatSkillPackSummary(packs) {
|
|
11578
|
-
const totalTokens = packs.reduce((sum, pack) => sum + pack.tokens, 0);
|
|
11579
|
-
const lines = [
|
|
11580
|
-
`Skill packs loaded: ${packs.length}`,
|
|
11581
|
-
"| slug | reason | tokens |",
|
|
11582
|
-
"|------|--------|--------|",
|
|
11583
|
-
...packs.map((pack) => `| ${pack.slug} | ${pack.reason} | ${pack.tokens} |`),
|
|
11584
|
-
`Total pack tokens: ${totalTokens} / ${SKILL_PACK_TOKEN_BUDGET}`
|
|
11585
|
-
];
|
|
11586
|
-
return lines.join("\n");
|
|
11587
|
-
}
|
|
11588
12690
|
async function askOnce(provider, prompt) {
|
|
11589
12691
|
let text2 = "";
|
|
11590
12692
|
for await (const delta of provider.streamChat({ messages: [{ role: "user", content: prompt }], tools: [] })) {
|
|
@@ -11596,20 +12698,31 @@ async function askOnce(provider, prompt) {
|
|
|
11596
12698
|
process.stdout.write("\n");
|
|
11597
12699
|
return text2;
|
|
11598
12700
|
}
|
|
11599
|
-
async function testProvider() {
|
|
12701
|
+
async function testProvider(args) {
|
|
12702
|
+
const requestedProvider = flagString(args, "provider") ?? "configured";
|
|
12703
|
+
if (envValue({}, "TANYA_RUN_LIVE_PROVIDER_TESTS") !== "1") {
|
|
12704
|
+
console.log(`skipped live provider test for ${requestedProvider}; set TANYA_RUN_LIVE_PROVIDER_TESTS=1 to run against the real endpoint.`);
|
|
12705
|
+
return;
|
|
12706
|
+
}
|
|
11600
12707
|
const config = loadConfig();
|
|
11601
12708
|
const provider = createProvider(config);
|
|
11602
12709
|
const startedAt = Date.now();
|
|
11603
12710
|
let text2 = "";
|
|
11604
12711
|
for await (const delta of provider.streamChat({
|
|
11605
|
-
messages: [
|
|
12712
|
+
messages: [
|
|
12713
|
+
{ role: "system", content: "You are a provider conformance probe. Keep answers short." },
|
|
12714
|
+
{ role: "user", content: "Reply with exactly: pong" },
|
|
12715
|
+
{ role: "user", content: "No tools are needed." }
|
|
12716
|
+
],
|
|
11606
12717
|
tools: [],
|
|
11607
12718
|
maxTokens: 12,
|
|
11608
12719
|
temperature: 0
|
|
11609
12720
|
})) {
|
|
11610
12721
|
if (delta.content) text2 += delta.content;
|
|
11611
12722
|
}
|
|
11612
|
-
console.log(`
|
|
12723
|
+
console.log(`PASS adapter: ${provider.id}:${provider.model}`);
|
|
12724
|
+
console.log(`PASS streaming-chat: ${Date.now() - startedAt}ms ${text2.trim()}`);
|
|
12725
|
+
console.log("PASS parser-surface: mock conformance covers malformed tool-call quirks in CI");
|
|
11613
12726
|
}
|
|
11614
12727
|
async function doctor(args) {
|
|
11615
12728
|
const cwd = resolve12(args ? flagString(args, "cwd") ?? process.cwd() : process.cwd());
|
|
@@ -11627,16 +12740,16 @@ async function doctor(args) {
|
|
|
11627
12740
|
else fail("provider.baseUrl", "missing \u2014 set TANYA_BASE_URL");
|
|
11628
12741
|
ok("provider.model", `${config.provider}:${config.model} (profile=${config.profile})`);
|
|
11629
12742
|
ok("provider.timeoutMs", `${config.timeoutMs}ms`);
|
|
11630
|
-
const cwdHasGit =
|
|
12743
|
+
const cwdHasGit = existsSync17(join22(cwd, ".git"));
|
|
11631
12744
|
if (cwdHasGit) ok("workspace.git", `${cwd}`);
|
|
11632
12745
|
else warn("workspace.git", `${cwd} is not a git repository \u2014 stash/retry recovery will be disabled`);
|
|
11633
|
-
const cwdHasArtifacts =
|
|
11634
|
-
if (cwdHasArtifacts) ok("workspace.artifacts", `${
|
|
12746
|
+
const cwdHasArtifacts = existsSync17(join22(cwd, "artifacts"));
|
|
12747
|
+
if (cwdHasArtifacts) ok("workspace.artifacts", `${join22(cwd, "artifacts")} (auto-detected)`);
|
|
11635
12748
|
else warn("workspace.artifacts", "no ./artifacts dir \u2014 pass --artifacts-root or run from a project that has one");
|
|
11636
|
-
const fpPath =
|
|
11637
|
-
if (
|
|
12749
|
+
const fpPath = join22(cwd, ".tania", "forbidden-patterns.json");
|
|
12750
|
+
if (existsSync17(fpPath)) {
|
|
11638
12751
|
try {
|
|
11639
|
-
const raw =
|
|
12752
|
+
const raw = readFileSync8(fpPath, "utf8");
|
|
11640
12753
|
const parsed = JSON.parse(raw);
|
|
11641
12754
|
const count = Array.isArray(parsed?.patterns) ? parsed.patterns.length : 0;
|
|
11642
12755
|
ok("workspace.forbiddenPatterns", `${count} project pattern(s) loaded from ${fpPath}`);
|
|
@@ -11646,10 +12759,10 @@ async function doctor(args) {
|
|
|
11646
12759
|
} else {
|
|
11647
12760
|
ok("workspace.forbiddenPatterns", "no project overrides (using defaults)");
|
|
11648
12761
|
}
|
|
11649
|
-
const fpMetricsPath =
|
|
11650
|
-
if (
|
|
12762
|
+
const fpMetricsPath = join22(cwd, ".tania", "memory", "forbidden-patterns-metrics.json");
|
|
12763
|
+
if (existsSync17(fpMetricsPath)) {
|
|
11651
12764
|
try {
|
|
11652
|
-
const raw =
|
|
12765
|
+
const raw = readFileSync8(fpMetricsPath, "utf8");
|
|
11653
12766
|
const parsed = JSON.parse(raw);
|
|
11654
12767
|
const totals = parsed.totals ?? {};
|
|
11655
12768
|
const top = Object.entries(totals).sort((a, b) => b[1] - a[1]).slice(0, 5);
|
|
@@ -11666,7 +12779,7 @@ async function doctor(args) {
|
|
|
11666
12779
|
ok("workspace.forbiddenPatterns.metrics", "no metrics yet (no scans recorded)");
|
|
11667
12780
|
}
|
|
11668
12781
|
if (config.obsidianVault) {
|
|
11669
|
-
if (
|
|
12782
|
+
if (existsSync17(config.obsidianVault)) ok("obsidian.vault", config.obsidianVault);
|
|
11670
12783
|
else warn("obsidian.vault", `${config.obsidianVault} configured but path does not exist`);
|
|
11671
12784
|
} else {
|
|
11672
12785
|
ok("obsidian.vault", "not configured (optional)");
|
|
@@ -11695,29 +12808,29 @@ async function runVideoCommand(args) {
|
|
|
11695
12808
|
if (preset === "presets" || preset === "list") {
|
|
11696
12809
|
console.log("Video presets:");
|
|
11697
12810
|
for (const item of videoPresets) {
|
|
11698
|
-
const
|
|
11699
|
-
console.log(`- ${item.name} (${item.width}x${item.height}, ${item.fps}fps, ${item.duration}s)${
|
|
12811
|
+
const aliases2 = item.aliases.length ? ` aliases: ${item.aliases.join(", ")}` : "";
|
|
12812
|
+
console.log(`- ${item.name} (${item.width}x${item.height}, ${item.fps}fps, ${item.duration}s)${aliases2}`);
|
|
11700
12813
|
console.log(` ${item.description}`);
|
|
11701
12814
|
}
|
|
11702
12815
|
return;
|
|
11703
12816
|
}
|
|
11704
12817
|
if (preset === "render-ad") {
|
|
11705
12818
|
const cwd2 = resolve12(flagString(args, "cwd") ?? process.cwd());
|
|
11706
|
-
const
|
|
11707
|
-
if (!
|
|
12819
|
+
const input = flagString(args, "input");
|
|
12820
|
+
if (!input) {
|
|
11708
12821
|
console.log("Usage: tanya video render-ad --input spec.json [--output-dir dir] [--basename name] [--format mp4] [--format poster]");
|
|
11709
12822
|
return;
|
|
11710
12823
|
}
|
|
11711
12824
|
const formats2 = flagStrings(args, "format");
|
|
11712
12825
|
const renderOptions = {
|
|
11713
|
-
input
|
|
12826
|
+
input,
|
|
11714
12827
|
formats: formats2.length ? formats2 : ["mp4", "poster"]
|
|
11715
12828
|
};
|
|
11716
12829
|
const outputDir2 = flagString(args, "output-dir") ?? flagString(args, "outputDir");
|
|
11717
|
-
const
|
|
12830
|
+
const basename6 = flagString(args, "basename");
|
|
11718
12831
|
const ffmpegPath = flagString(args, "ffmpeg-path") ?? flagString(args, "ffmpegPath");
|
|
11719
12832
|
if (outputDir2) renderOptions.outputDir = outputDir2;
|
|
11720
|
-
if (
|
|
12833
|
+
if (basename6) renderOptions.basename = basename6;
|
|
11721
12834
|
if (ffmpegPath) renderOptions.ffmpegPath = ffmpegPath;
|
|
11722
12835
|
const result2 = await renderFullAd(renderOptions, cwd2);
|
|
11723
12836
|
console.log(JSON.stringify({
|
|
@@ -11739,7 +12852,7 @@ async function runVideoCommand(args) {
|
|
|
11739
12852
|
const formats = flagStrings(args, "format");
|
|
11740
12853
|
const options = { preset };
|
|
11741
12854
|
const outputDir = flagString(args, "output-dir") ?? flagString(args, "outputDir");
|
|
11742
|
-
const
|
|
12855
|
+
const basename5 = flagString(args, "basename");
|
|
11743
12856
|
const width = flagNumber(args, "width");
|
|
11744
12857
|
const height = flagNumber(args, "height");
|
|
11745
12858
|
const fps = flagNumber(args, "fps");
|
|
@@ -11750,7 +12863,7 @@ async function runVideoCommand(args) {
|
|
|
11750
12863
|
const badge = flagString(args, "badge");
|
|
11751
12864
|
const lines = flagStrings(args, "line");
|
|
11752
12865
|
if (outputDir) options.outputDir = outputDir;
|
|
11753
|
-
if (
|
|
12866
|
+
if (basename5) options.basename = basename5;
|
|
11754
12867
|
if (width !== void 0) options.width = width;
|
|
11755
12868
|
if (height !== void 0) options.height = height;
|
|
11756
12869
|
if (fps !== void 0) options.fps = fps;
|
|
@@ -11767,6 +12880,7 @@ async function runVideoCommand(args) {
|
|
|
11767
12880
|
}
|
|
11768
12881
|
async function main() {
|
|
11769
12882
|
const args = parseArgs(process.argv.slice(2));
|
|
12883
|
+
applyCliProviderFlag(args);
|
|
11770
12884
|
if (args.command === "help" || args.command === "--help" || args.command === "-h") {
|
|
11771
12885
|
console.log(usage());
|
|
11772
12886
|
return;
|
|
@@ -11777,13 +12891,13 @@ async function main() {
|
|
|
11777
12891
|
}
|
|
11778
12892
|
if (args.command === "patterns") {
|
|
11779
12893
|
const cwd2 = resolve12(flagString(args, "cwd") ?? process.cwd());
|
|
11780
|
-
const metricsPath =
|
|
11781
|
-
if (!
|
|
12894
|
+
const metricsPath = join22(cwd2, ".tania", "memory", "forbidden-patterns-metrics.json");
|
|
12895
|
+
if (!existsSync17(metricsPath)) {
|
|
11782
12896
|
console.log(`No metrics file at ${metricsPath}. Run a tanya task in this workspace first.`);
|
|
11783
12897
|
return;
|
|
11784
12898
|
}
|
|
11785
12899
|
try {
|
|
11786
|
-
const parsed = JSON.parse(
|
|
12900
|
+
const parsed = JSON.parse(readFileSync8(metricsPath, "utf8"));
|
|
11787
12901
|
const totals = parsed.totals ?? {};
|
|
11788
12902
|
const lastFiredAt = parsed.lastFiredAt ?? {};
|
|
11789
12903
|
const entries = Object.entries(totals).sort((a, b) => b[1] - a[1]);
|
|
@@ -11807,10 +12921,10 @@ async function main() {
|
|
|
11807
12921
|
}
|
|
11808
12922
|
if (args.command === "providers") {
|
|
11809
12923
|
if (args.positional[0] !== "test") {
|
|
11810
|
-
console.log("Usage: tanya providers test");
|
|
12924
|
+
console.log("Usage: tanya providers test --provider <name>");
|
|
11811
12925
|
return;
|
|
11812
12926
|
}
|
|
11813
|
-
await testProvider();
|
|
12927
|
+
await testProvider(args);
|
|
11814
12928
|
return;
|
|
11815
12929
|
}
|
|
11816
12930
|
if (args.command === "init") {
|
|
@@ -11840,31 +12954,13 @@ async function main() {
|
|
|
11840
12954
|
}
|
|
11841
12955
|
if (args.command === "runs") {
|
|
11842
12956
|
const cwd2 = resolve12(flagString(args, "cwd") ?? process.cwd());
|
|
11843
|
-
const
|
|
11844
|
-
if (
|
|
12957
|
+
const logs = readRunLogs(cwd2, 10);
|
|
12958
|
+
if (logs.length === 0) {
|
|
11845
12959
|
process.stdout.write("No run logs found. Run tanya run first.\n");
|
|
11846
12960
|
return;
|
|
11847
12961
|
}
|
|
11848
|
-
const
|
|
11849
|
-
|
|
11850
|
-
if (files.length === 0) {
|
|
11851
|
-
process.stdout.write("No run logs found.\n");
|
|
11852
|
-
return;
|
|
11853
|
-
}
|
|
11854
|
-
for (const file of files) {
|
|
11855
|
-
try {
|
|
11856
|
-
const log = JSON.parse(readFileSync8(join20(runsDir, file), "utf8"));
|
|
11857
|
-
const cost = estimateRunCost(log.model, log.promptTokens ?? 0, log.completionTokens ?? 0);
|
|
11858
|
-
const status = log.blockers?.length > 0 ? "BLOCKED" : "OK";
|
|
11859
|
-
const duration = `${Math.round((log.durationMs ?? 0) / 1e3)}s`;
|
|
11860
|
-
const fileCount = log.changedFiles?.length ?? 0;
|
|
11861
|
-
process.stdout.write(
|
|
11862
|
-
`${log.ts?.slice(0, 16)} ${status.padEnd(7)} ${duration.padStart(5)} ${cost.padStart(8)} ${fileCount} file(s) ${log.prompt?.slice(0, 60)}
|
|
11863
|
-
`
|
|
11864
|
-
);
|
|
11865
|
-
} catch {
|
|
11866
|
-
}
|
|
11867
|
-
}
|
|
12962
|
+
for (const log of logs) process.stdout.write(`${formatRunLogLine(log)}
|
|
12963
|
+
`);
|
|
11868
12964
|
return;
|
|
11869
12965
|
}
|
|
11870
12966
|
applyCliProfileFlag(args);
|
|
@@ -11878,9 +12974,9 @@ async function main() {
|
|
|
11878
12974
|
const systemPrompt = buildSystemPrompt(cwd2, runContext, historyBlock, task);
|
|
11879
12975
|
const skillPacks = loadPromptSkillPacks(cwd2, runContext, task);
|
|
11880
12976
|
const sections = flagStrings(args, "section");
|
|
11881
|
-
const
|
|
12977
|
+
const output = selectPromptSections(systemPrompt, sections);
|
|
11882
12978
|
process.stdout.write("=== SYSTEM PROMPT ===\n\n");
|
|
11883
|
-
process.stdout.write(
|
|
12979
|
+
process.stdout.write(output);
|
|
11884
12980
|
process.stdout.write("\n\n=== END SYSTEM PROMPT ===\n");
|
|
11885
12981
|
if (sections.length === 0) {
|
|
11886
12982
|
process.stdout.write(`
|
|
@@ -12053,7 +13149,12 @@ ${postBlockers.map((blocker) => ` - ${blocker}`).join("\n")}
|
|
|
12053
13149
|
}
|
|
12054
13150
|
}
|
|
12055
13151
|
if (runPromptTokens > 0 || runCompletionTokens > 0) {
|
|
12056
|
-
const costStr = estimateRunCost(
|
|
13152
|
+
const costStr = estimateRunCost({
|
|
13153
|
+
provider: config.provider,
|
|
13154
|
+
model: config.model,
|
|
13155
|
+
promptTokens: runPromptTokens,
|
|
13156
|
+
completionTokens: runCompletionTokens
|
|
13157
|
+
}).display;
|
|
12057
13158
|
process.stderr.write(
|
|
12058
13159
|
`[tanya] Tokens: ${runPromptTokens.toLocaleString()} in / ${runCompletionTokens.toLocaleString()} out ${costStr}
|
|
12059
13160
|
`
|