@ai-sdk/harness-cline 1.0.19 → 1.0.20

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/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # @ai-sdk/harness-cline
2
2
 
3
+ ## 1.0.20
4
+
5
+ ### Patch Changes
6
+
7
+ - 7608210: feat(harness): add `model` parameter to `HarnessAgent` instead of having each harness adapter support it on their own constructor functions
8
+ - f7bd978: chore(harness): update underlying adapter SDKs to their latest versions
9
+ - 14d4fc0: feat(harness): allow changing harness settings between turns via `prepareCall()` support on `HarnessAgent`
10
+ - 5021768: fix(harness-cline): use native `skills` tool instead of manual file-based workaround
11
+ - Updated dependencies [cc9f6ce]
12
+ - Updated dependencies [7608210]
13
+ - Updated dependencies [6f8a2d7]
14
+ - Updated dependencies [14d4fc0]
15
+ - Updated dependencies [90192f1]
16
+ - @ai-sdk/harness@1.0.93
17
+ - @ai-sdk/provider-utils@5.0.33
18
+
3
19
  ## 1.0.19
4
20
 
5
21
  ### Patch Changes
package/dist/index.d.ts CHANGED
@@ -26,6 +26,8 @@ type ClineHarnessSettings = {
26
26
  /**
27
27
  * Model id for the configured provider. When omitted, Cline selects the
28
28
  * provider's default model.
29
+ *
30
+ * @deprecated Use `model` on `HarnessAgent` instead.
29
31
  */
30
32
  readonly modelId?: string;
31
33
  /**
@@ -80,6 +82,7 @@ declare const CLINE_BUILTIN_TOOLS: {
80
82
  limit?: number | undefined;
81
83
  }, unknown>;
82
84
  readonly ls: HarnessV1BuiltinTool;
85
+ readonly skills: HarnessV1BuiltinTool;
83
86
  };
84
87
  declare function createCline(settings?: ClineHarnessSettings): HarnessV1<typeof CLINE_BUILTIN_TOOLS>;
85
88
 
@@ -122,6 +125,7 @@ declare const cline: _ai_sdk_harness.HarnessV1<{
122
125
  limit?: number | undefined;
123
126
  }, unknown>;
124
127
  readonly ls: _ai_sdk_harness.HarnessV1BuiltinTool;
128
+ readonly skills: _ai_sdk_harness.HarnessV1BuiltinTool;
125
129
  }>;
126
130
 
127
131
  export { type ClineAuthenticationMode, type ClineHarnessSettings, VERSION, cline, createCline };
package/dist/index.js CHANGED
@@ -241,7 +241,6 @@ import { shellQuote } from "@ai-sdk/harness/utils";
241
241
  function createClineRemoteOps(options) {
242
242
  const { sandbox, workDir } = options;
243
243
  const normalizedWorkDir = path2.posix.normalize(workDir);
244
- const readableRoots = options.readableRoots?.map((root) => path2.posix.normalize(root.sandboxDir)) ?? [];
245
244
  const isInsidePath = ({
246
245
  parent,
247
246
  candidate
@@ -256,25 +255,10 @@ function createClineRemoteOps(options) {
256
255
  }
257
256
  return normalized;
258
257
  };
259
- const assertReadablePath = (inputPath) => {
260
- const normalized = path2.posix.normalize(inputPath);
261
- if (!isInsidePath({ parent: normalizedWorkDir, candidate: normalized }) && !readableRoots.some(
262
- (parent) => isInsidePath({ parent, candidate: normalized })
263
- )) {
264
- throw new Error(`Cline path escapes the readable roots: ${inputPath}`);
265
- }
266
- return normalized;
267
- };
268
258
  const resolvePath = (inputPath) => {
269
259
  const resolved = path2.posix.isAbsolute(inputPath) ? path2.posix.normalize(inputPath) : path2.posix.normalize(path2.posix.join(normalizedWorkDir, inputPath));
270
260
  return assertWorkspacePath(resolved);
271
261
  };
272
- const resolveReadablePath = (inputPath) => {
273
- if (!path2.posix.isAbsolute(inputPath)) {
274
- return resolvePath(inputPath);
275
- }
276
- return assertReadablePath(inputPath);
277
- };
278
262
  const runShell = async ({
279
263
  command,
280
264
  signal
@@ -319,7 +303,7 @@ function createClineRemoteOps(options) {
319
303
  remotePath,
320
304
  inputPath,
321
305
  missingMessage
322
- }) => assertReadablePath(
306
+ }) => assertWorkspacePath(
323
307
  await resolveExistingSandboxPath({
324
308
  remotePath,
325
309
  inputPath,
@@ -352,7 +336,7 @@ function createClineRemoteOps(options) {
352
336
  return assertWorkspacePath(resolvedPath);
353
337
  };
354
338
  const readFile = async (inputPath) => {
355
- const remotePath = resolveReadablePath(inputPath);
339
+ const remotePath = resolvePath(inputPath);
356
340
  const resolved = await resolveReadableSandboxPath({
357
341
  remotePath,
358
342
  inputPath,
@@ -420,7 +404,7 @@ function createClineRemoteOps(options) {
420
404
  },
421
405
  async grep(pattern, input = {}) {
422
406
  const inputPath = input.path ?? ".";
423
- const remotePath = resolveReadablePath(inputPath);
407
+ const remotePath = resolvePath(inputPath);
424
408
  const target = await resolveReadableSandboxPath({
425
409
  remotePath,
426
410
  inputPath
@@ -452,7 +436,7 @@ function createClineRemoteOps(options) {
452
436
  return output || "No matches found";
453
437
  },
454
438
  async glob(pattern, inputPath = ".", limit = 1e3) {
455
- const remotePath = resolveReadablePath(inputPath);
439
+ const remotePath = resolvePath(inputPath);
456
440
  const target = await resolveReadableSandboxPath({
457
441
  remotePath,
458
442
  inputPath
@@ -483,7 +467,7 @@ function createClineRemoteOps(options) {
483
467
  ).slice(0, limit);
484
468
  },
485
469
  async ls(inputPath = ".", limit = 500) {
486
- const remotePath = resolveReadablePath(inputPath);
470
+ const remotePath = resolvePath(inputPath);
487
471
  const target = await resolveReadableSandboxPath({
488
472
  remotePath,
489
473
  inputPath
@@ -516,33 +500,123 @@ function createClineRemoteOps(options) {
516
500
 
517
501
  // src/cline-skills.ts
518
502
  import path3 from "path";
519
- import { writeSkills } from "@ai-sdk/harness/utils";
520
- async function writeClineSkills({
521
- sandbox,
522
- sandboxHomeDir,
523
- skills,
524
- abortSignal
503
+ import { createDefaultTools } from "@cline/core";
504
+ var CLINE_SKILL_NAME_PATTERN = /^[A-Za-z0-9._-]+$/;
505
+ function createClineSkillsRuntime({
506
+ skills
525
507
  }) {
526
- if (skills.length === 0) return void 0;
527
- const skillRootDir = path3.posix.join(sandboxHomeDir, ".agents", "skills");
528
- await writeSkills({
529
- sandbox,
530
- rootDir: skillRootDir,
531
- skills,
532
- ...abortSignal ? { abortSignal } : {}
533
- });
534
- return skillRootDir;
508
+ const projectedSkills = projectClineSkills({ skills });
509
+ const signature = JSON.stringify(projectedSkills);
510
+ if (projectedSkills.length === 0) {
511
+ return { signature };
512
+ }
513
+ const executor = async (...executorArguments) => {
514
+ const [requestedSkill, args] = executorArguments;
515
+ const skill = projectedSkills.find(
516
+ (candidate) => candidate.id === normalizeSkillToken(requestedSkill)
517
+ );
518
+ if (skill == null) {
519
+ const availableSkills = projectedSkills.map((skill2) => skill2.name);
520
+ return availableSkills.length > 0 ? `Skill "${requestedSkill}" not found. Available skills: ${availableSkills.join(", ")}` : "No skills are currently available.";
521
+ }
522
+ return renderSkillInstructions({ skill, args });
523
+ };
524
+ executor.configuredSkills = projectedSkills.map((skill) => ({
525
+ id: skill.id,
526
+ name: skill.name,
527
+ description: skill.description,
528
+ disabled: false
529
+ }));
530
+ const tool2 = createDefaultTools({
531
+ executors: { skills: executor },
532
+ enableReadFiles: false,
533
+ enableSearch: false,
534
+ enableBash: false,
535
+ enableWebFetch: false,
536
+ enableApplyPatch: false,
537
+ enableEditor: false,
538
+ enableSkills: true,
539
+ enableAskQuestion: false,
540
+ enableSubmitAndExit: false
541
+ }).find((tool3) => tool3.name === "skills");
542
+ if (tool2 == null) {
543
+ throw new Error("Cline did not create its native skills tool.");
544
+ }
545
+ return { signature, tool: tool2 };
546
+ }
547
+ function projectClineSkills({
548
+ skills
549
+ }) {
550
+ const names = /* @__PURE__ */ new Set();
551
+ const ids = /* @__PURE__ */ new Set();
552
+ return skills.map((skill) => {
553
+ if (!CLINE_SKILL_NAME_PATTERN.test(skill.name) || skill.name === "." || skill.name === "..") {
554
+ throw new Error(`Invalid Cline skill name: ${skill.name}`);
555
+ }
556
+ if (names.has(skill.name)) {
557
+ throw new Error(`Duplicate Cline skill name: ${skill.name}`);
558
+ }
559
+ names.add(skill.name);
560
+ const id = normalizeSkillToken(skill.name);
561
+ if (ids.has(id)) {
562
+ throw new Error(`Duplicate Cline skill identifier: ${id}`);
563
+ }
564
+ ids.add(id);
565
+ return {
566
+ id,
567
+ name: skill.name,
568
+ description: skill.description,
569
+ content: skill.content,
570
+ files: (skill.files ?? []).map((file) => ({
571
+ path: normalizeSkillFilePath({
572
+ skillName: skill.name,
573
+ filePath: file.path
574
+ }),
575
+ content: file.content
576
+ })).sort(
577
+ (left, right) => left.path.localeCompare(right.path) || left.content.localeCompare(right.content)
578
+ )
579
+ };
580
+ }).sort((left, right) => left.name.localeCompare(right.name));
581
+ }
582
+ function normalizeSkillToken(skill) {
583
+ return skill.trim().replace(/^\/+/, "").toLowerCase();
584
+ }
585
+ function normalizeSkillFilePath({
586
+ skillName,
587
+ filePath
588
+ }) {
589
+ const normalized = path3.posix.normalize(filePath);
590
+ if (normalized === "" || normalized === "." || normalized.startsWith("../") || normalized.includes("/../") || normalized.endsWith("/..") || path3.posix.isAbsolute(normalized)) {
591
+ throw new Error(
592
+ `Invalid Cline skill file path for ${skillName}: ${filePath}`
593
+ );
594
+ }
595
+ return normalized;
535
596
  }
536
- function renderSkillsPromptSection(skills, skillRootDir) {
537
- const entries = skills.map(
538
- (skill) => `- ${skill.name}: ${skill.description}
539
- SKILL.md: ${path3.posix.join(
540
- skillRootDir,
541
- skill.name,
542
- "SKILL.md"
543
- )}`
544
- ).join("\n");
545
- return "## Skills\n\nThe following skills are available. When a task matches a skill, read its SKILL.md with the `read` tool and follow its instructions.\n\n" + entries;
597
+ function renderSkillInstructions({
598
+ skill,
599
+ args
600
+ }) {
601
+ const trimmedArgs = args?.trim();
602
+ const argsTag = trimmedArgs ? `
603
+ <command-args>${trimmedArgs}</command-args>` : "";
604
+ const description = skill.description.trim() ? `Description: ${skill.description.trim()}
605
+
606
+ ` : "";
607
+ const files = skill.files.length === 0 ? "" : `
608
+
609
+ <skill-files>
610
+ ${skill.files.map(
611
+ (file) => `<skill-file path=${JSON.stringify(file.path)}>
612
+ ${file.content}
613
+ </skill-file>`
614
+ ).join("\n")}
615
+ </skill-files>`;
616
+ return `<command-name>${skill.name}</command-name>${argsTag}
617
+ <command-instructions>
618
+ ${description}${skill.content}${files}
619
+ </command-instructions>`;
546
620
  }
547
621
 
548
622
  // src/cline-tools.ts
@@ -657,7 +731,8 @@ var CLINE_NATIVE_BUILTIN_NAMES = [
657
731
  "bash",
658
732
  "grep",
659
733
  "glob",
660
- "ls"
734
+ "ls",
735
+ "skills"
661
736
  ];
662
737
  var CLINE_NATIVE_TOOL_KINDS = {
663
738
  read: "readonly",
@@ -666,7 +741,8 @@ var CLINE_NATIVE_TOOL_KINDS = {
666
741
  bash: "bash",
667
742
  grep: "readonly",
668
743
  glob: "readonly",
669
- ls: "readonly"
744
+ ls: "readonly",
745
+ skills: "readonly"
670
746
  };
671
747
  function isClineBuiltinToolName(name) {
672
748
  return CLINE_NATIVE_BUILTIN_NAMES.includes(name);
@@ -1110,7 +1186,7 @@ function clineBuiltinToolRequiresApproval(input) {
1110
1186
  return input.kind === "edit" || input.kind === "bash";
1111
1187
  }
1112
1188
  function buildSystemPrompt(input) {
1113
- const sections = [
1189
+ return [
1114
1190
  "You are Cline, an autonomous coding agent operating inside a sandboxed workspace.",
1115
1191
  `## Workspace
1116
1192
 
@@ -1118,11 +1194,7 @@ The workspace root is \`${input.sessionWorkDir}\`. All relative file paths and s
1118
1194
  `## Sandbox
1119
1195
 
1120
1196
  ${input.sandboxDescription}`
1121
- ];
1122
- if (input.skillsSection) {
1123
- sections.push(input.skillsSection);
1124
- }
1125
- return sections.join("\n\n");
1197
+ ].join("\n\n");
1126
1198
  }
1127
1199
  function toClineBackendProviderBaseUrl(baseUrl) {
1128
1200
  return `${baseUrl.replace(/\/+$/, "")}/api/v1`;
@@ -1199,28 +1271,9 @@ async function createClineSession(input) {
1199
1271
  const activeBuiltinNames = resolveActiveClineBuiltinNames(
1200
1272
  input.builtinToolFiltering
1201
1273
  );
1202
- let skillsSection;
1203
- let skillRootDir;
1204
- if (input.skills.length > 0) {
1205
- skillRootDir = await writeClineSkills({
1206
- sandbox: toolSafeSandboxSession,
1207
- sandboxHomeDir,
1208
- skills: input.skills,
1209
- ...input.abortSignal ? { abortSignal: input.abortSignal } : {}
1210
- });
1211
- if (skillRootDir) {
1212
- skillsSection = renderSkillsPromptSection(input.skills, skillRootDir);
1213
- }
1214
- }
1215
1274
  const ops = createClineRemoteOps({
1216
1275
  sandbox: toolSafeSandboxSession,
1217
- workDir: input.sessionWorkDir,
1218
- ...skillRootDir ? { readableRoots: [{ sandboxDir: skillRootDir }] } : {}
1219
- });
1220
- const baseSystemPrompt = buildSystemPrompt({
1221
- sessionWorkDir: input.sessionWorkDir,
1222
- sandboxDescription: toolSafeSandboxSession.description,
1223
- ...skillsSection ? { skillsSection } : {}
1276
+ workDir: input.sessionWorkDir
1224
1277
  });
1225
1278
  let currentMessages = [];
1226
1279
  if (input.isResume && input.resumeHistoryFileName) {
@@ -1243,7 +1296,7 @@ async function createClineSession(input) {
1243
1296
  });
1244
1297
  let agent;
1245
1298
  let unsubscribe;
1246
- let lastToolsSignature;
1299
+ let lastAgentConfigurationSignature;
1247
1300
  let agentHasRun = false;
1248
1301
  let stopped = false;
1249
1302
  let suspending = false;
@@ -1314,14 +1367,21 @@ async function createClineSession(input) {
1314
1367
  }
1315
1368
  } : {},
1316
1369
  sessionId: input.sessionId,
1317
- systemPrompt: rebuildInput.instructions ? `${baseSystemPrompt}
1370
+ systemPrompt: (() => {
1371
+ const baseSystemPrompt = buildSystemPrompt({
1372
+ sessionWorkDir: input.sessionWorkDir,
1373
+ sandboxDescription: toolSafeSandboxSession.description
1374
+ });
1375
+ return rebuildInput.instructions ? `${baseSystemPrompt}
1318
1376
 
1319
- ${rebuildInput.instructions}` : baseSystemPrompt,
1377
+ ${rebuildInput.instructions}` : baseSystemPrompt;
1378
+ })(),
1320
1379
  initialMessages: currentMessages,
1321
1380
  toolExecution: "parallel",
1322
1381
  ...input.settings.maxIterations !== void 0 ? { maxIterations: input.settings.maxIterations } : {},
1323
1382
  tools: [
1324
1383
  ...buildBuiltinAgentTools({ ops, activeNames: activeBuiltinNames }),
1384
+ ...rebuildInput.skillsRuntime.tool && activeBuiltinNames.includes("skills") ? [rebuildInput.skillsRuntime.tool] : [],
1325
1385
  ...mcpRuntime.tools,
1326
1386
  ...buildUserAgentTools({
1327
1387
  specs: rebuildInput.userTools,
@@ -1438,11 +1498,14 @@ ${rebuildInput.instructions}` : baseSystemPrompt,
1438
1498
  done: controlInput.done
1439
1499
  };
1440
1500
  }
1441
- function runTurn(turnOpts) {
1501
+ async function runTurn(turnOpts) {
1442
1502
  if (stopped) {
1443
1503
  throw new Error("Cline session has been stopped.");
1444
1504
  }
1445
1505
  const userTools = turnOpts.tools;
1506
+ const skillsRuntime = createClineSkillsRuntime({
1507
+ skills: turnOpts.skills
1508
+ });
1446
1509
  if (turnOpts.responseFormat?.type === "json" && turnOpts.responseFormat.schema == null) {
1447
1510
  throw new HarnessCapabilityUnsupportedError2({
1448
1511
  message: "Harness 'cline' requires a JSON schema for structured output.",
@@ -1456,22 +1519,28 @@ ${rebuildInput.instructions}` : baseSystemPrompt,
1456
1519
  });
1457
1520
  }
1458
1521
  const signature = JSON.stringify({
1459
- tools: userTools.map((t) => t.name).sort(),
1460
- responseFormat: turnOpts.responseFormat
1522
+ tools: userTools,
1523
+ responseFormat: turnOpts.responseFormat,
1524
+ instructions: turnOpts.instructions,
1525
+ skills: skillsRuntime.signature
1461
1526
  });
1462
- if (agent == null || signature !== lastToolsSignature) {
1527
+ if (agent == null || signature !== lastAgentConfigurationSignature) {
1463
1528
  currentMessages = agent?.snapshot().messages ?? currentMessages;
1464
1529
  rebuildAgent({
1465
1530
  userTools,
1531
+ skillsRuntime,
1466
1532
  responseFormat: turnOpts.responseFormat,
1467
1533
  ...turnOpts.instructions ? { instructions: turnOpts.instructions } : {}
1468
1534
  });
1469
- lastToolsSignature = signature;
1535
+ lastAgentConfigurationSignature = signature;
1470
1536
  }
1471
1537
  currentEmit = turnOpts.emit;
1538
+ const userToolNames = new Set(userTools.map((tool2) => tool2.name));
1472
1539
  translatorState = createClineTranslatorState({
1473
- builtinToolNames: activeBuiltinNames,
1474
- hostToolNames: userTools.map((tool2) => tool2.name),
1540
+ builtinToolNames: activeBuiltinNames.filter(
1541
+ (name) => !userToolNames.has(name)
1542
+ ),
1543
+ hostToolNames: userToolNames,
1475
1544
  ignoredToolNames: turnOpts.responseFormat?.type === "json" ? ["structured_output"] : [],
1476
1545
  mcpToolNames: mcpRuntime.toolNames
1477
1546
  });
@@ -1610,6 +1679,7 @@ ${rebuildInput.instructions}` : baseSystemPrompt,
1610
1679
  doPromptTurn: async (promptOpts) => {
1611
1680
  return runTurn({
1612
1681
  text: extractUserText(promptOpts.prompt),
1682
+ skills: promptOpts.skills,
1613
1683
  tools: promptOpts.tools ?? [],
1614
1684
  ...promptOpts.instructions ? { instructions: promptOpts.instructions } : {},
1615
1685
  emit: promptOpts.emit,
@@ -1627,6 +1697,7 @@ ${rebuildInput.instructions}` : baseSystemPrompt,
1627
1697
  }
1628
1698
  return runTurn({
1629
1699
  text: void 0,
1700
+ skills: continueOpts.skills,
1630
1701
  tools: continueOpts.tools ?? [],
1631
1702
  ...continueOpts.instructions ? { instructions: continueOpts.instructions } : {},
1632
1703
  emit: continueOpts.emit,
@@ -1710,7 +1781,7 @@ ${rebuildInput.instructions}` : baseSystemPrompt,
1710
1781
  }
1711
1782
 
1712
1783
  // src/version.ts
1713
- var VERSION = true ? "1.0.19" : "0.0.0-test";
1784
+ var VERSION = true ? "1.0.20" : "0.0.0-test";
1714
1785
 
1715
1786
  // src/cline-harness.ts
1716
1787
  var CLINE_CLIENT_APP = `ai-sdk/harness-cline/${VERSION}`;
@@ -1786,6 +1857,18 @@ var CLINE_BUILTIN_TOOLS = {
1786
1857
  }),
1787
1858
  nativeName: "ls",
1788
1859
  toolUseKind: "readonly"
1860
+ },
1861
+ skills: {
1862
+ ...tool({
1863
+ description: "Execute a configured skill by name.",
1864
+ inputSchema: z2.object({
1865
+ skill: z2.string(),
1866
+ args: z2.string().optional()
1867
+ }),
1868
+ outputSchema: z2.string()
1869
+ }),
1870
+ nativeName: "skills",
1871
+ toolUseKind: "readonly"
1789
1872
  }
1790
1873
  };
1791
1874
  function createCline(settings = {}) {
@@ -1797,6 +1880,7 @@ function createCline(settings = {}) {
1797
1880
  supportsBuiltinToolFiltering: true,
1798
1881
  lifecycleStateSchema: clineResumeStateSchema,
1799
1882
  doStart: async (startOpts) => {
1883
+ const modelId = startOpts.model ?? settings.modelId;
1800
1884
  const authEnv = resolveClineEnv({ auth: settings.auth });
1801
1885
  const lifecycleState = startOpts.continueFrom ?? startOpts.resumeFrom;
1802
1886
  const resumeData = lifecycleState?.data;
@@ -1804,13 +1888,12 @@ function createCline(settings = {}) {
1804
1888
  sessionId: startOpts.sessionId,
1805
1889
  sandboxSession: startOpts.sandboxSession,
1806
1890
  sessionWorkDir: startOpts.sessionWorkDir,
1807
- skills: startOpts.skills ?? [],
1808
1891
  settings: {
1809
1892
  authEnv,
1810
1893
  isAuthenticationEnvironmentOverride: isHarnessAuthenticationEnvironment2(settings.auth),
1811
1894
  ...settings.mcpServers ? { mcpServers: settings.mcpServers } : {},
1812
1895
  ...settings.providerId ? { providerId: settings.providerId } : {},
1813
- ...settings.modelId ? { modelId: settings.modelId } : {},
1896
+ ...modelId == null ? {} : { modelId },
1814
1897
  ...settings.apiKey ? { apiKey: settings.apiKey } : {},
1815
1898
  ...settings.baseUrl ? { baseUrl: settings.baseUrl } : {},
1816
1899
  ...settings.headers ? { headers: settings.headers } : {},