zidane 2.0.1 → 2.2.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.
Files changed (36) hide show
  1. package/README.md +40 -26
  2. package/dist/{agent-D-ZFMbSd.d.ts → agent-vPBFXnu-.d.ts} +389 -274
  3. package/dist/{chunk-SZA4FKW5.js → chunk-2EQT4EHD.js} +4 -3
  4. package/dist/{chunk-PJUUYBKF.js → chunk-37GD7NL3.js} +45 -16
  5. package/dist/{chunk-LVC7NQUZ.js → chunk-BW3WTFIR.js} +1 -1
  6. package/dist/{chunk-FRNFVKWW.js → chunk-CDRXC7A7.js} +64 -33
  7. package/dist/{chunk-PASFWG7S.js → chunk-F5UBXERT.js} +309 -77
  8. package/dist/{chunk-7JTBBZ2U.js → chunk-LNN5UTS2.js} +8 -0
  9. package/dist/{chunk-VG2E6YK3.js → chunk-PMCQOMV4.js} +4 -2
  10. package/dist/{chunk-LN4LLLHA.js → chunk-S3FCOMRI.js} +63 -20
  11. package/dist/{chunk-OVQ4N64O.js → chunk-SP5NA6WF.js} +6 -12
  12. package/dist/{chunk-BCXXXJ3G.js → chunk-TPXPVEH6.js} +99 -58
  13. package/dist/contexts.js +1 -1
  14. package/dist/index.d.ts +6 -5
  15. package/dist/index.js +16 -16
  16. package/dist/mcp.d.ts +1 -1
  17. package/dist/mcp.js +1 -1
  18. package/dist/presets.d.ts +33 -0
  19. package/dist/presets.js +15 -0
  20. package/dist/providers.d.ts +1 -1
  21. package/dist/providers.js +3 -3
  22. package/dist/session/sqlite.d.ts +1 -1
  23. package/dist/session.d.ts +1 -1
  24. package/dist/session.js +3 -3
  25. package/dist/{skills-use-C4KFVla0.d.ts → skills-use-39cCsA7_.d.ts} +4 -4
  26. package/dist/skills.d.ts +3 -9
  27. package/dist/skills.js +3 -5
  28. package/dist/spawn-Czx3owjX.d.ts +152 -0
  29. package/dist/tools.d.ts +6 -4
  30. package/dist/tools.js +5 -5
  31. package/dist/types.d.ts +3 -2
  32. package/dist/types.js +1 -1
  33. package/package.json +5 -5
  34. package/dist/harnesses.d.ts +0 -4
  35. package/dist/harnesses.js +0 -17
  36. package/dist/spawn-RoqpjYLZ.d.ts +0 -99
@@ -165,10 +165,11 @@ function createProcessContext(config) {
165
165
  });
166
166
  return { stdout, stderr, exitCode: 0 };
167
167
  } catch (err) {
168
+ const e = err;
168
169
  return {
169
- stdout: err.stdout ?? "",
170
- stderr: err.stderr ?? err.message,
171
- exitCode: err.code ?? 1
170
+ stdout: typeof e.stdout === "string" ? e.stdout : "",
171
+ stderr: typeof e.stderr === "string" ? e.stderr : typeof e.message === "string" ? e.message : String(err),
172
+ exitCode: typeof e.code === "number" ? e.code : 1
172
173
  };
173
174
  }
174
175
  },
@@ -69,8 +69,11 @@ function resultToString(content) {
69
69
  if (!content || !Array.isArray(content))
70
70
  return "";
71
71
  return content.map((block) => {
72
- if (block?.type === "text")
73
- return block.text;
72
+ if (block && typeof block === "object" && block.type === "text") {
73
+ const text = block.text;
74
+ if (typeof text === "string")
75
+ return text;
76
+ }
74
77
  return JSON.stringify(block);
75
78
  }).join("\n");
76
79
  }
@@ -144,6 +147,7 @@ async function connectMcpServers(configs, _clientFactory, hooks) {
144
147
  const connections = [];
145
148
  const tools = {};
146
149
  const errors = [];
150
+ let closed = false;
147
151
  for (const config of configs) {
148
152
  try {
149
153
  const client = _clientFactory ? _clientFactory() : new Client({ name: "zidane", version: "1.0.0" });
@@ -162,8 +166,8 @@ async function connectMcpServers(configs, _clientFactory, hooks) {
162
166
  inputSchema: tool.inputSchema ?? { type: "object", properties: {} }
163
167
  },
164
168
  execute: async (input, ctx) => {
165
- const { turnId, callId } = ctx;
166
- const displayName = ctx.harness?.toolAliases?.[namespacedName] ?? namespacedName;
169
+ const { turnId, callId, signal } = ctx;
170
+ const displayName = ctx.toolAliases?.[namespacedName] ?? namespacedName;
167
171
  const gateCtx = {
168
172
  turnId,
169
173
  callId,
@@ -188,13 +192,12 @@ async function connectMcpServers(configs, _clientFactory, hooks) {
188
192
  });
189
193
  const timeout = config.toolTimeout ?? 3e4;
190
194
  try {
191
- let timer;
192
- const result = await Promise.race([
193
- client.callTool({ name: tool.name, arguments: effectiveInput }),
194
- new Promise((_, reject) => {
195
- timer = setTimeout(() => reject(new Error(`MCP tool "${tool.name}" on server "${config.name}" timed out after ${timeout}ms`)), timeout);
196
- })
197
- ]).finally(() => clearTimeout(timer));
195
+ const result = await raceWithTimeoutAndSignal(
196
+ () => client.callTool({ name: tool.name, arguments: effectiveInput }),
197
+ timeout,
198
+ `MCP tool "${tool.name}" on server "${config.name}" timed out after ${timeout}ms`,
199
+ signal
200
+ );
198
201
  let output = packMcpResult(result.content);
199
202
  const transformCtx = {
200
203
  turnId,
@@ -218,6 +221,7 @@ async function connectMcpServers(configs, _clientFactory, hooks) {
218
221
  });
219
222
  return output;
220
223
  } catch (err) {
224
+ const error = err instanceof Error ? err : new Error(String(err));
221
225
  await hooks?.callHook("mcp:tool:error", {
222
226
  turnId,
223
227
  callId,
@@ -225,7 +229,7 @@ async function connectMcpServers(configs, _clientFactory, hooks) {
225
229
  tool: tool.name,
226
230
  displayName,
227
231
  input: effectiveInput,
228
- error: err
232
+ error
229
233
  });
230
234
  await hooks?.callHook("mcp:tool:after", {
231
235
  turnId,
@@ -234,9 +238,9 @@ async function connectMcpServers(configs, _clientFactory, hooks) {
234
238
  tool: tool.name,
235
239
  displayName,
236
240
  input: effectiveInput,
237
- result: err.message ?? String(err)
241
+ result: error.message
238
242
  });
239
- throw err;
243
+ throw error;
240
244
  }
241
245
  }
242
246
  };
@@ -247,8 +251,9 @@ async function connectMcpServers(configs, _clientFactory, hooks) {
247
251
  tools: toolNames
248
252
  });
249
253
  } catch (err) {
250
- errors.push({ name: config.name, error: err });
251
- await hooks?.callHook("mcp:error", { name: config.name, error: err });
254
+ const error = err instanceof Error ? err : new Error(String(err));
255
+ errors.push({ name: config.name, error });
256
+ await hooks?.callHook("mcp:error", { name: config.name, error });
252
257
  }
253
258
  }
254
259
  if (errors.length > 0 && connections.length === 0) {
@@ -258,6 +263,9 @@ async function connectMcpServers(configs, _clientFactory, hooks) {
258
263
  return {
259
264
  tools,
260
265
  close: async () => {
266
+ if (closed)
267
+ return;
268
+ closed = true;
261
269
  await Promise.allSettled(
262
270
  connections.map(async ({ name, client }) => {
263
271
  await hooks?.callHook("mcp:close", { name });
@@ -267,6 +275,27 @@ async function connectMcpServers(configs, _clientFactory, hooks) {
267
275
  }
268
276
  };
269
277
  }
278
+ async function raceWithTimeoutAndSignal(task, timeoutMs, timeoutMessage, signal) {
279
+ if (signal?.aborted)
280
+ throw new Error("MCP tool call aborted");
281
+ let timer;
282
+ let onAbort;
283
+ try {
284
+ return await new Promise((resolvePromise, rejectPromise) => {
285
+ timer = setTimeout(() => rejectPromise(new Error(timeoutMessage)), timeoutMs);
286
+ if (signal) {
287
+ onAbort = () => rejectPromise(new Error("MCP tool call aborted"));
288
+ signal.addEventListener("abort", onAbort, { once: true });
289
+ }
290
+ task().then(resolvePromise, rejectPromise);
291
+ });
292
+ } finally {
293
+ if (timer)
294
+ clearTimeout(timer);
295
+ if (signal && onAbort)
296
+ signal.removeEventListener("abort", onAbort);
297
+ }
298
+ }
270
299
 
271
300
  export {
272
301
  normalizeMcpServers,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  validateSkillForWrite
3
- } from "./chunk-BCXXXJ3G.js";
3
+ } from "./chunk-TPXPVEH6.js";
4
4
 
5
5
  // src/skills/index.ts
6
6
  function defineSkill(config) {
@@ -5,27 +5,39 @@ import {
5
5
  toAnthropic,
6
6
  toolResultsMessage,
7
7
  userMessage
8
- } from "./chunk-LN4LLLHA.js";
8
+ } from "./chunk-S3FCOMRI.js";
9
9
  import {
10
10
  matchesContextExceeded
11
- } from "./chunk-7JTBBZ2U.js";
12
-
13
- // src/providers/anthropic.ts
14
- import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
15
- import { resolve as resolve2 } from "path";
11
+ } from "./chunk-LNN5UTS2.js";
16
12
 
17
13
  // src/providers/oauth.ts
18
- import { existsSync, readFileSync, writeFileSync } from "fs";
14
+ import { existsSync, readFileSync, renameSync, writeFileSync } from "fs";
19
15
  import { resolve } from "path";
20
16
  import { getOAuthApiKey } from "@yaelg/pi-ai/oauth";
21
- var CREDENTIALS_FILE = resolve(process.cwd(), ".credentials.json");
17
+ function credentialsFilePath() {
18
+ return resolve(process.cwd(), ".credentials.json");
19
+ }
20
+ var CREDENTIALS_FILE_MODE = 384;
21
+ var refreshLocks = /* @__PURE__ */ new Map();
22
22
  function readOAuthCredentials() {
23
- if (!existsSync(CREDENTIALS_FILE))
23
+ const path = credentialsFilePath();
24
+ if (!existsSync(path))
24
25
  return {};
25
- return JSON.parse(readFileSync(CREDENTIALS_FILE, "utf-8"));
26
+ try {
27
+ const raw = readFileSync(path, "utf-8");
28
+ const parsed = JSON.parse(raw);
29
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
30
+ return {};
31
+ return parsed;
32
+ } catch {
33
+ return {};
34
+ }
26
35
  }
27
36
  function writeOAuthCredentials(credentials) {
28
- writeFileSync(CREDENTIALS_FILE, JSON.stringify(credentials, null, 2));
37
+ const path = credentialsFilePath();
38
+ const tmp = `${path}.${process.pid}.${Date.now()}.tmp`;
39
+ writeFileSync(tmp, JSON.stringify(credentials, null, 2), { mode: CREDENTIALS_FILE_MODE });
40
+ renameSync(tmp, path);
29
41
  }
30
42
  function credentialsFromParams(params, extraKeys = []) {
31
43
  if (typeof params?.access !== "string" || typeof params.refresh !== "string" || typeof params.expires !== "number")
@@ -45,7 +57,10 @@ async function resolveOAuthApiKey(options, callbacks) {
45
57
  return options.params.apiKey;
46
58
  const paramsCredentials = credentialsFromParams(options.params, options.extraCredentialKeys);
47
59
  if (paramsCredentials) {
48
- return await resolveCredentialSource("params", paramsCredentials);
60
+ return await withRefreshLock(
61
+ `params:${options.providerId}`,
62
+ () => resolveCredentialSource("params", paramsCredentials)
63
+ );
49
64
  }
50
65
  if (typeof options.params?.access === "string")
51
66
  return options.params.access;
@@ -53,21 +68,23 @@ async function resolveOAuthApiKey(options, callbacks) {
53
68
  return process.env[options.envKey];
54
69
  const readCredentials = options.readCredentials ?? readOAuthCredentials;
55
70
  const writeCredentials = options.writeCredentials ?? writeOAuthCredentials;
56
- const allCredentials = readCredentials();
57
- const storedCredentials = allCredentials[options.providerId];
58
- if (!storedCredentials)
59
- throw new Error(options.missingError);
60
- return await resolveCredentialSource("file", storedCredentials, allCredentials, writeCredentials);
61
- async function resolveCredentialSource(source, current, allCredentials2, persistCredentials) {
71
+ return await withRefreshLock(`file:${options.providerId}`, async () => {
72
+ const allCredentials = readCredentials();
73
+ const storedCredentials = allCredentials[options.providerId];
74
+ if (!storedCredentials)
75
+ throw new Error(options.missingError);
76
+ return await resolveCredentialSource("file", storedCredentials, allCredentials, writeCredentials);
77
+ });
78
+ async function resolveCredentialSource(source, current, allCredentials, persistCredentials) {
62
79
  try {
63
80
  const refreshOAuthApiKey = options.getOAuthApiKey ?? getOAuthApiKey;
64
81
  const result = await refreshOAuthApiKey(options.providerId, { [options.providerId]: current });
65
82
  if (!result)
66
83
  throw new Error(options.missingError);
67
84
  if (result.newCredentials !== current) {
68
- if (source === "file" && allCredentials2 && persistCredentials) {
69
- allCredentials2[options.providerId] = result.newCredentials;
70
- persistCredentials(allCredentials2);
85
+ if (source === "file" && allCredentials && persistCredentials) {
86
+ allCredentials[options.providerId] = result.newCredentials;
87
+ persistCredentials(allCredentials);
71
88
  }
72
89
  await callbacks?.onOAuthRefresh?.({
73
90
  provider: options.provider,
@@ -84,9 +101,22 @@ async function resolveOAuthApiKey(options, callbacks) {
84
101
  }
85
102
  }
86
103
  }
104
+ async function withRefreshLock(key, fn) {
105
+ const existing = refreshLocks.get(key);
106
+ if (existing)
107
+ return existing;
108
+ const task = (async () => {
109
+ try {
110
+ return await fn();
111
+ } finally {
112
+ refreshLocks.delete(key);
113
+ }
114
+ })();
115
+ refreshLocks.set(key, task);
116
+ return task;
117
+ }
87
118
 
88
119
  // src/providers/anthropic.ts
89
- var CREDENTIALS_FILE2 = resolve2(process.cwd(), ".credentials.json");
90
120
  var _sdkCtor = null;
91
121
  async function loadAnthropicSdk() {
92
122
  if (_sdkCtor)
@@ -109,11 +139,9 @@ function getConfiguredApiKey(anthropicParams) {
109
139
  return anthropicParams.access;
110
140
  if (process.env.ANTHROPIC_API_KEY)
111
141
  return process.env.ANTHROPIC_API_KEY;
112
- if (existsSync2(CREDENTIALS_FILE2)) {
113
- const creds = JSON.parse(readFileSync2(CREDENTIALS_FILE2, "utf-8"));
114
- if (creds.anthropic?.access)
115
- return creds.anthropic.access;
116
- }
142
+ const access = readOAuthCredentials().anthropic?.access;
143
+ if (typeof access === "string" && access.length > 0)
144
+ return access;
117
145
  throw new Error("No API key found. Run `bun run auth` first.");
118
146
  }
119
147
  function createClient(SDK, apiKey, isOAuth, baseURL) {
@@ -183,10 +211,13 @@ function classifyAnthropicError(err) {
183
211
  message
184
212
  };
185
213
  }
214
+ const status = anyErr.status;
215
+ const retryable = typeof status === "number" ? status === 429 || status >= 500 && status !== 501 : void 0;
186
216
  return {
187
217
  kind: "provider_error",
188
- providerCode: nativeType ?? (anyErr.status ? String(anyErr.status) : void 0),
189
- message
218
+ providerCode: nativeType ?? (status ? String(status) : void 0),
219
+ message,
220
+ ...retryable !== void 0 ? { retryable } : {}
190
221
  };
191
222
  }
192
223
  function anthropicPromptMessage(parts) {
@@ -368,11 +399,11 @@ function anthropic(anthropicParams) {
368
399
  // src/providers/cerebras.ts
369
400
  var BASE_URL = "https://api.cerebras.ai/v1";
370
401
  function getApiKey(params) {
371
- if (params?.apiKey)
402
+ if (typeof params?.apiKey === "string" && params.apiKey.length > 0)
372
403
  return params.apiKey;
373
404
  if (process.env.CEREBRAS_API_KEY)
374
405
  return process.env.CEREBRAS_API_KEY;
375
- throw new Error("No Cerebras API key found. Set CEREBRAS_API_KEY in your environment.");
406
+ throw new Error("No Cerebras API key found. Pass `apiKey` or set CEREBRAS_API_KEY in your environment.");
376
407
  }
377
408
  function cerebras(params) {
378
409
  const apiKey = getApiKey(params);
@@ -650,11 +681,11 @@ function openai(params) {
650
681
  // src/providers/openrouter.ts
651
682
  var BASE_URL2 = "https://openrouter.ai/api/v1";
652
683
  function getApiKey2(params) {
653
- if (params?.apiKey)
684
+ if (typeof params?.apiKey === "string" && params.apiKey.length > 0)
654
685
  return params.apiKey;
655
686
  if (process.env.OPENROUTER_API_KEY)
656
687
  return process.env.OPENROUTER_API_KEY;
657
- throw new Error("No OpenRouter API key found. Set OPENROUTER_API_KEY in your environment.");
688
+ throw new Error("No OpenRouter API key found. Pass `apiKey` or set OPENROUTER_API_KEY in your environment.");
658
689
  }
659
690
  function openrouter(params) {
660
691
  const apiKey = getApiKey2(params);