@matheuskrumenauer/tanya 0.3.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/dist/cli.js +768 -95
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -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);
|
|
@@ -482,6 +653,39 @@ function createCosmoSink(stream = process.stdout) {
|
|
|
482
653
|
runId: event.runId
|
|
483
654
|
});
|
|
484
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;
|
|
485
689
|
case "final":
|
|
486
690
|
ensureEventLine();
|
|
487
691
|
if (event.message.trim()) {
|
|
@@ -531,6 +735,9 @@ var knownEventTypes = /* @__PURE__ */ new Set([
|
|
|
531
735
|
"tool_cancel_requested",
|
|
532
736
|
"tool_cancelled",
|
|
533
737
|
"tool_result",
|
|
738
|
+
"tool_call_parse_warning",
|
|
739
|
+
"schema_flatten_warning",
|
|
740
|
+
"provider_throttle",
|
|
534
741
|
"command_invoked",
|
|
535
742
|
"final",
|
|
536
743
|
"error"
|
|
@@ -548,27 +755,237 @@ function createJsonlSink(stream = process.stdout) {
|
|
|
548
755
|
};
|
|
549
756
|
}
|
|
550
757
|
|
|
551
|
-
// src/providers/
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
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 };
|
|
556
764
|
}
|
|
557
|
-
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()) {
|
|
558
895
|
const raw = response.headers.get("retry-after");
|
|
559
896
|
if (!raw) return null;
|
|
560
897
|
const seconds = Number(raw);
|
|
561
|
-
if (Number.isFinite(seconds) && seconds >= 0) return Math.
|
|
898
|
+
if (Number.isFinite(seconds) && seconds >= 0) return Math.max(0, seconds * 1e3);
|
|
562
899
|
const dateMs = Date.parse(raw);
|
|
563
|
-
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
|
+
}
|
|
564
916
|
return null;
|
|
565
917
|
}
|
|
566
|
-
function
|
|
567
|
-
const
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
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;
|
|
572
989
|
}
|
|
573
990
|
var OpenAiCompatibleProvider = class {
|
|
574
991
|
id;
|
|
@@ -578,11 +995,13 @@ var OpenAiCompatibleProvider = class {
|
|
|
578
995
|
timeoutMs;
|
|
579
996
|
temperature;
|
|
580
997
|
topP;
|
|
998
|
+
adapter;
|
|
581
999
|
constructor(options) {
|
|
1000
|
+
this.adapter = resolveProviderAdapter({ provider: options.id, baseUrl: options.baseUrl });
|
|
582
1001
|
this.id = options.id;
|
|
583
1002
|
this.apiKey = options.apiKey;
|
|
584
|
-
this.baseUrl = options.baseUrl.replace(/\/$/, "");
|
|
585
|
-
this.model = options.model;
|
|
1003
|
+
this.baseUrl = (options.baseUrl || this.adapter.defaultBaseUrl || "").replace(/\/$/, "");
|
|
1004
|
+
this.model = options.model || this.adapter.defaultModel || "";
|
|
586
1005
|
const envTimeout = parseInt(envValue({}, "TANYA_TIMEOUT_MS"), 10);
|
|
587
1006
|
const envTimeoutMs = Number.isFinite(envTimeout) && envTimeout > 0 ? envTimeout : null;
|
|
588
1007
|
this.timeoutMs = envTimeoutMs ?? options.timeoutMs ?? 9e4;
|
|
@@ -599,54 +1018,62 @@ var OpenAiCompatibleProvider = class {
|
|
|
599
1018
|
if (timeout) clearTimeout(timeout);
|
|
600
1019
|
timeout = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
601
1020
|
};
|
|
602
|
-
const
|
|
1021
|
+
const request = {
|
|
603
1022
|
model: this.model,
|
|
604
1023
|
messages: input.messages,
|
|
605
|
-
tools: input.tools?.length ? input.tools : void 0,
|
|
606
|
-
tool_choice: input.tools?.length ? "auto" : void 0,
|
|
607
1024
|
temperature: input.temperature ?? this.temperature,
|
|
608
1025
|
top_p: input.topP ?? this.topP,
|
|
609
1026
|
max_tokens: input.maxTokens ?? 8192,
|
|
610
1027
|
stream: true
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
Authorization: `Bearer ${this.apiKey}`,
|
|
621
|
-
"Content-Type": "application/json"
|
|
622
|
-
},
|
|
623
|
-
body: requestBody
|
|
624
|
-
}).catch((error) => {
|
|
625
|
-
if (controller.signal.aborted) {
|
|
626
|
-
throw new Error(`Provider ${this.id} timed out before streaming a response.`);
|
|
627
|
-
}
|
|
628
|
-
throw error;
|
|
629
|
-
});
|
|
630
|
-
if (response.ok && response.body) break;
|
|
631
|
-
if (!RETRYABLE_HTTP_STATUSES.has(response.status) || attempt === MAX_FETCH_ATTEMPTS - 1) {
|
|
632
|
-
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;
|
|
633
1037
|
}
|
|
634
|
-
|
|
635
|
-
await sleep(backoffMs(attempt, response));
|
|
636
|
-
}
|
|
637
|
-
if (!response) {
|
|
638
|
-
if (timeout) clearTimeout(timeout);
|
|
639
|
-
throw new Error(`Provider ${this.id} failed to open a streaming response.`);
|
|
1038
|
+
request.tool_choice = "auto";
|
|
640
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);
|
|
641
1065
|
if (!response.ok || !response.body) {
|
|
642
1066
|
if (timeout) clearTimeout(timeout);
|
|
643
|
-
const detail =
|
|
1067
|
+
const detail = await response.text().catch(() => "");
|
|
644
1068
|
throw new Error(`Provider ${this.id} returned HTTP ${response.status}: ${detail.slice(0, 500)}`);
|
|
645
1069
|
}
|
|
646
1070
|
const decoder = new TextDecoder();
|
|
647
1071
|
let buffer = "";
|
|
648
1072
|
const toolCallParts = /* @__PURE__ */ new Map();
|
|
649
1073
|
try {
|
|
1074
|
+
if (schemaWarnings.length > 0) {
|
|
1075
|
+
yield { schemaWarnings };
|
|
1076
|
+
}
|
|
650
1077
|
for await (const chunk of response.body) {
|
|
651
1078
|
resetTimeout();
|
|
652
1079
|
buffer += decoder.decode(chunk, { stream: true });
|
|
@@ -659,7 +1086,8 @@ var OpenAiCompatibleProvider = class {
|
|
|
659
1086
|
if (data === "[DONE]") return;
|
|
660
1087
|
let parsed;
|
|
661
1088
|
try {
|
|
662
|
-
|
|
1089
|
+
const rawParsed = JSON.parse(data);
|
|
1090
|
+
parsed = this.adapter.postResponse ? this.adapter.postResponse(rawParsed) : rawParsed;
|
|
663
1091
|
} catch (error) {
|
|
664
1092
|
if (envValue({}, "TANYA_DEBUG")) {
|
|
665
1093
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -712,7 +1140,7 @@ var OpenAiCompatibleProvider = class {
|
|
|
712
1140
|
// src/providers/factory.ts
|
|
713
1141
|
function createProvider(config) {
|
|
714
1142
|
return new OpenAiCompatibleProvider({
|
|
715
|
-
id: config.profile === "reasoner" ? "deepseek-reasoner" : config.provider,
|
|
1143
|
+
id: config.provider === "deepseek" && config.profile === "reasoner" ? "deepseek-reasoner" : config.provider,
|
|
716
1144
|
apiKey: config.apiKey,
|
|
717
1145
|
baseUrl: config.baseUrl,
|
|
718
1146
|
model: config.model,
|
|
@@ -722,6 +1150,161 @@ function createProvider(config) {
|
|
|
722
1150
|
});
|
|
723
1151
|
}
|
|
724
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
|
+
|
|
725
1308
|
// src/safety/workspace.ts
|
|
726
1309
|
import { existsSync as existsSync3, lstatSync, realpathSync } from "fs";
|
|
727
1310
|
import { dirname as dirname2, resolve as resolve3, relative as relative2 } from "path";
|
|
@@ -1418,7 +2001,7 @@ function terminalSvg(config, frame) {
|
|
|
1418
2001
|
${body}
|
|
1419
2002
|
</svg>`;
|
|
1420
2003
|
}
|
|
1421
|
-
function
|
|
2004
|
+
function sleep(ms) {
|
|
1422
2005
|
return new Promise((resolve13) => setTimeout(resolve13, ms));
|
|
1423
2006
|
}
|
|
1424
2007
|
async function waitForJson(url, timeout = 1e4) {
|
|
@@ -1429,7 +2012,7 @@ async function waitForJson(url, timeout = 1e4) {
|
|
|
1429
2012
|
if (res.ok) return await res.json();
|
|
1430
2013
|
} catch {
|
|
1431
2014
|
}
|
|
1432
|
-
await
|
|
2015
|
+
await sleep(120);
|
|
1433
2016
|
}
|
|
1434
2017
|
throw new Error(`Timed out waiting for ${url}`);
|
|
1435
2018
|
}
|
|
@@ -1495,7 +2078,7 @@ async function captureFrames(config, frameDir, svgDir, chromeProfile) {
|
|
|
1495
2078
|
await page.send("Emulation.setDefaultBackgroundColorOverride", { color: { r: 0, g: 0, b: 0, a: 0 } });
|
|
1496
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>`;
|
|
1497
2080
|
await page.send("Page.navigate", { url: `data:text/html;charset=utf-8,${encodeURIComponent(html)}` });
|
|
1498
|
-
await
|
|
2081
|
+
await sleep(250);
|
|
1499
2082
|
const totalFrames = Math.round(config.fps * config.duration);
|
|
1500
2083
|
for (let i = 0; i < totalFrames; i += 1) {
|
|
1501
2084
|
const n = String(i + 1).padStart(4, "0");
|
|
@@ -5177,15 +5760,15 @@ function metadataBoolean(runContext, key) {
|
|
|
5177
5760
|
const value = runContext?.metadata?.[key];
|
|
5178
5761
|
return value === true || value === "true" || value === "yes";
|
|
5179
5762
|
}
|
|
5180
|
-
function
|
|
5763
|
+
function isRecord3(value) {
|
|
5181
5764
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
5182
5765
|
}
|
|
5183
5766
|
function metadataRecord(runContext, key) {
|
|
5184
5767
|
const value = runContext?.metadata?.[key];
|
|
5185
|
-
return
|
|
5768
|
+
return isRecord3(value) ? value : void 0;
|
|
5186
5769
|
}
|
|
5187
5770
|
function recordArray2(value) {
|
|
5188
|
-
return Array.isArray(value) ? value.filter(
|
|
5771
|
+
return Array.isArray(value) ? value.filter(isRecord3) : [];
|
|
5189
5772
|
}
|
|
5190
5773
|
function autoBriefRecords(runContext, key) {
|
|
5191
5774
|
return recordArray2(metadataRecord(runContext, "autoBrief")?.[key]);
|
|
@@ -8610,25 +9193,6 @@ import { cp as cp2, mkdir as mkdir10, rm as rm2, stat as stat6 } from "fs/promis
|
|
|
8610
9193
|
import { dirname as dirname12, isAbsolute as isAbsolute3, join as join15, relative as relative9, resolve as resolve9 } from "path";
|
|
8611
9194
|
var CONTEXT_TOKEN_LIMIT = 48e3;
|
|
8612
9195
|
var CONTEXT_SUMMARY_KEEP_RECENT = 6;
|
|
8613
|
-
function isMalformedToolArguments(value) {
|
|
8614
|
-
return Boolean(value && typeof value === "object" && value.ok === false && typeof value.rawArguments === "string");
|
|
8615
|
-
}
|
|
8616
|
-
function previewRawToolArguments(raw) {
|
|
8617
|
-
return raw.length > 500 ? `${raw.slice(0, 500)}...[truncated ${raw.length - 500} chars]` : raw;
|
|
8618
|
-
}
|
|
8619
|
-
function parseToolArguments(raw) {
|
|
8620
|
-
if (!raw.trim()) return {};
|
|
8621
|
-
try {
|
|
8622
|
-
return JSON.parse(raw);
|
|
8623
|
-
} catch {
|
|
8624
|
-
const rawPreview = previewRawToolArguments(raw);
|
|
8625
|
-
return {
|
|
8626
|
-
ok: false,
|
|
8627
|
-
error: `malformed tool arguments: ${rawPreview}`,
|
|
8628
|
-
rawArguments: rawPreview
|
|
8629
|
-
};
|
|
8630
|
-
}
|
|
8631
|
-
}
|
|
8632
9196
|
function findSafeCompressionBoundary(messages, desiredKeepCount) {
|
|
8633
9197
|
if (messages.length <= desiredKeepCount + 1) return Math.max(1, messages.length - desiredKeepCount);
|
|
8634
9198
|
let startIndex = messages.length - desiredKeepCount;
|
|
@@ -9027,6 +9591,7 @@ async function runAgent(options) {
|
|
|
9027
9591
|
let createdArtifactPaths = [];
|
|
9028
9592
|
const requiredTool = requiredHighLevelTool(options.runContext, options.prompt);
|
|
9029
9593
|
let requiredToolUsed = requiredTool ? false : true;
|
|
9594
|
+
let toolCallCorrectionAttempts = 0;
|
|
9030
9595
|
async function syncArtifactOutput() {
|
|
9031
9596
|
const outputRootValue = options.runContext?.metadata?.artifactOutputRoot;
|
|
9032
9597
|
if (typeof outputRootValue !== "string" || !outputRootValue.trim()) return [];
|
|
@@ -9095,7 +9660,7 @@ async function runAgent(options) {
|
|
|
9095
9660
|
}
|
|
9096
9661
|
await options.sink({ type: "message_start" });
|
|
9097
9662
|
let assistantText = "";
|
|
9098
|
-
let
|
|
9663
|
+
let rawToolCalls = [];
|
|
9099
9664
|
const codingProviderOptions = isCodingTask(options.runContext) ? { temperature: 0, topP: 0.2 } : {};
|
|
9100
9665
|
let providerAttempt = 0;
|
|
9101
9666
|
const PROVIDER_TRANSIENT_RETRIES = 1;
|
|
@@ -9104,24 +9669,44 @@ async function runAgent(options) {
|
|
|
9104
9669
|
for await (const delta of options.provider.streamChat({
|
|
9105
9670
|
messages,
|
|
9106
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
|
+
},
|
|
9107
9681
|
...codingProviderOptions
|
|
9108
9682
|
})) {
|
|
9109
9683
|
if (delta.usage) {
|
|
9110
9684
|
totalPromptTokens += delta.usage.promptTokens;
|
|
9111
9685
|
totalCompletionTokens += delta.usage.completionTokens;
|
|
9112
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
|
+
}
|
|
9113
9698
|
if (delta.content) {
|
|
9114
9699
|
assistantText += delta.content;
|
|
9115
9700
|
finalText += delta.content;
|
|
9116
9701
|
await options.sink({ type: "message_delta", text: delta.content });
|
|
9117
9702
|
}
|
|
9118
|
-
if (delta.toolCalls?.length)
|
|
9703
|
+
if (delta.toolCalls?.length) rawToolCalls = delta.toolCalls;
|
|
9119
9704
|
}
|
|
9120
9705
|
break;
|
|
9121
9706
|
} catch (err) {
|
|
9122
9707
|
const message2 = err instanceof Error ? err.message : String(err);
|
|
9123
9708
|
const isTransient = /timed out|fetch failed|ECONNRESET|EAI_AGAIN|ENOTFOUND|socket hang up/i.test(message2);
|
|
9124
|
-
const noProgressYet = assistantText.length === 0 &&
|
|
9709
|
+
const noProgressYet = assistantText.length === 0 && rawToolCalls.length === 0;
|
|
9125
9710
|
if (isTransient && noProgressYet && providerAttempt < PROVIDER_TRANSIENT_RETRIES) {
|
|
9126
9711
|
providerAttempt += 1;
|
|
9127
9712
|
await options.sink({ type: "status", message: `Provider transient error (${message2.slice(0, 120)}); retrying same turn (${providerAttempt}/${PROVIDER_TRANSIENT_RETRIES}).` });
|
|
@@ -9131,6 +9716,62 @@ async function runAgent(options) {
|
|
|
9131
9716
|
}
|
|
9132
9717
|
}
|
|
9133
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;
|
|
9134
9775
|
const assistantMessage = {
|
|
9135
9776
|
role: "assistant",
|
|
9136
9777
|
content: assistantText || null
|
|
@@ -9197,16 +9838,16 @@ async function runAgent(options) {
|
|
|
9197
9838
|
for (const toolCall2 of toolCalls) {
|
|
9198
9839
|
const toolName = toolCall2.function.name;
|
|
9199
9840
|
const tool = registry.get(toolName);
|
|
9200
|
-
const
|
|
9201
|
-
if (
|
|
9841
|
+
const parsedInput = parseToolArguments(toolCall2.function.arguments);
|
|
9842
|
+
if (!parsedInput.ok) {
|
|
9202
9843
|
toolErrorCount += 1;
|
|
9203
9844
|
messages.push({
|
|
9204
9845
|
role: "tool",
|
|
9205
9846
|
tool_call_id: toolCall2.id,
|
|
9206
9847
|
content: JSON.stringify({
|
|
9207
9848
|
ok: false,
|
|
9208
|
-
error:
|
|
9209
|
-
rawArguments:
|
|
9849
|
+
error: parsedInput.reason,
|
|
9850
|
+
rawArguments: parsedInput.rawArguments
|
|
9210
9851
|
})
|
|
9211
9852
|
});
|
|
9212
9853
|
await options.sink({
|
|
@@ -9215,11 +9856,12 @@ async function runAgent(options) {
|
|
|
9215
9856
|
tool: toolName,
|
|
9216
9857
|
ok: false,
|
|
9217
9858
|
summary: "Invalid tool arguments (malformed JSON).",
|
|
9218
|
-
output: `raw arguments (preview): ${
|
|
9219
|
-
error:
|
|
9859
|
+
output: `raw arguments (preview): ${parsedInput.rawArguments}`,
|
|
9860
|
+
error: parsedInput.reason
|
|
9220
9861
|
});
|
|
9221
9862
|
continue;
|
|
9222
9863
|
}
|
|
9864
|
+
const callInput = parsedInput.input;
|
|
9223
9865
|
toolCallCount += 1;
|
|
9224
9866
|
await options.sink({ type: "tool_call", id: toolCall2.id, tool: toolName, input: callInput });
|
|
9225
9867
|
if (!tool) {
|
|
@@ -10090,6 +10732,19 @@ ${toolGlyph} ${event.tool}
|
|
|
10090
10732
|
break;
|
|
10091
10733
|
case "command_invoked":
|
|
10092
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}.
|
|
10746
|
+
`);
|
|
10747
|
+
break;
|
|
10093
10748
|
case "final":
|
|
10094
10749
|
stream.write(`
|
|
10095
10750
|
${event.message.trim()}
|
|
@@ -11696,6 +12351,7 @@ var cliOptionDefinitions = [
|
|
|
11696
12351
|
{ flags: "--output-dir <path>", key: "output-dir", kind: "string", property: "outputDir", aliases: ["outputDir"] },
|
|
11697
12352
|
{ flags: "--plan", key: "plan", kind: "boolean", property: "plan" },
|
|
11698
12353
|
{ flags: "--profile <id>", key: "profile", kind: "string", property: "profile" },
|
|
12354
|
+
{ flags: "--provider <name>", key: "provider", kind: "string", property: "provider" },
|
|
11699
12355
|
{ flags: "--prompt-file <path>", key: "prompt-file", kind: "string", property: "promptFile" },
|
|
11700
12356
|
{ flags: "--repair-attempts <count>", key: "repair-attempts", kind: "string", property: "repairAttempts" },
|
|
11701
12357
|
{ flags: "--require-verification <command>", key: "require-verification", kind: "array", property: "requireVerification" },
|
|
@@ -11842,6 +12498,11 @@ function applyCliProfileFlag(args) {
|
|
|
11842
12498
|
if (!profile) return;
|
|
11843
12499
|
process.env.TANYA_PROFILE = profile;
|
|
11844
12500
|
}
|
|
12501
|
+
function applyCliProviderFlag(args) {
|
|
12502
|
+
const provider = flagString(args, "provider");
|
|
12503
|
+
if (!provider) return;
|
|
12504
|
+
process.env.TANYA_PROVIDER = provider;
|
|
12505
|
+
}
|
|
11845
12506
|
function buildRetryContext(manifest, attempt, extraBlockers = []) {
|
|
11846
12507
|
const lines = [
|
|
11847
12508
|
`RETRY CONTEXT (attempt ${attempt}): the previous run did not complete cleanly.`,
|
|
@@ -11907,7 +12568,7 @@ Usage:
|
|
|
11907
12568
|
tanya benchmark profiles List benchmark profiles
|
|
11908
12569
|
tanya benchmark run --all Run executable regression benchmarks
|
|
11909
12570
|
tanya benchmark validate Validate recent benchmark signatures
|
|
11910
|
-
tanya providers test
|
|
12571
|
+
tanya providers test --provider deepseek Test provider configuration (live only with TANYA_RUN_LIVE_PROVIDER_TESTS=1)
|
|
11911
12572
|
tanya doctor Check local setup
|
|
11912
12573
|
tanya patterns Show forbidden-pattern fire metrics for this workspace
|
|
11913
12574
|
|
|
@@ -12037,20 +12698,31 @@ async function askOnce(provider, prompt) {
|
|
|
12037
12698
|
process.stdout.write("\n");
|
|
12038
12699
|
return text2;
|
|
12039
12700
|
}
|
|
12040
|
-
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
|
+
}
|
|
12041
12707
|
const config = loadConfig();
|
|
12042
12708
|
const provider = createProvider(config);
|
|
12043
12709
|
const startedAt = Date.now();
|
|
12044
12710
|
let text2 = "";
|
|
12045
12711
|
for await (const delta of provider.streamChat({
|
|
12046
|
-
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
|
+
],
|
|
12047
12717
|
tools: [],
|
|
12048
12718
|
maxTokens: 12,
|
|
12049
12719
|
temperature: 0
|
|
12050
12720
|
})) {
|
|
12051
12721
|
if (delta.content) text2 += delta.content;
|
|
12052
12722
|
}
|
|
12053
|
-
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");
|
|
12054
12726
|
}
|
|
12055
12727
|
async function doctor(args) {
|
|
12056
12728
|
const cwd = resolve12(args ? flagString(args, "cwd") ?? process.cwd() : process.cwd());
|
|
@@ -12136,8 +12808,8 @@ async function runVideoCommand(args) {
|
|
|
12136
12808
|
if (preset === "presets" || preset === "list") {
|
|
12137
12809
|
console.log("Video presets:");
|
|
12138
12810
|
for (const item of videoPresets) {
|
|
12139
|
-
const
|
|
12140
|
-
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}`);
|
|
12141
12813
|
console.log(` ${item.description}`);
|
|
12142
12814
|
}
|
|
12143
12815
|
return;
|
|
@@ -12208,6 +12880,7 @@ async function runVideoCommand(args) {
|
|
|
12208
12880
|
}
|
|
12209
12881
|
async function main() {
|
|
12210
12882
|
const args = parseArgs(process.argv.slice(2));
|
|
12883
|
+
applyCliProviderFlag(args);
|
|
12211
12884
|
if (args.command === "help" || args.command === "--help" || args.command === "-h") {
|
|
12212
12885
|
console.log(usage());
|
|
12213
12886
|
return;
|
|
@@ -12248,10 +12921,10 @@ async function main() {
|
|
|
12248
12921
|
}
|
|
12249
12922
|
if (args.command === "providers") {
|
|
12250
12923
|
if (args.positional[0] !== "test") {
|
|
12251
|
-
console.log("Usage: tanya providers test");
|
|
12924
|
+
console.log("Usage: tanya providers test --provider <name>");
|
|
12252
12925
|
return;
|
|
12253
12926
|
}
|
|
12254
|
-
await testProvider();
|
|
12927
|
+
await testProvider(args);
|
|
12255
12928
|
return;
|
|
12256
12929
|
}
|
|
12257
12930
|
if (args.command === "init") {
|