Skip to content

Commit 7b87bbe

Browse files
Copilotpelikhan
andauthored
Fully merge addon implementation into rig.ts
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
1 parent b284c6d commit 7b87bbe

52 files changed

Lines changed: 123 additions & 203 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

skills/rig/SKILL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ Run inline input or a program file with `node skills/rig/rig.ts`; add `--server`
134134

135135
- Known context uses `p.*`; true runtime data uses `input`.
136136
- Important outputs are explicitly typed and constrained.
137-
- Every helper and import uses the current `rig` or `rig/addons` API.
137+
- Every helper and import uses the current `rig` API.
138138
- Generated TypeScript passes `node skills/rig/eslint/lint.js <program.ts>` and typechecking.
139139
- Every subagent is named, reachable, and narrowly scoped.
140140
- Snippets have one default export and no `console.log`.

skills/rig/addons.ts

Lines changed: 2 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -1,102 +1,2 @@
1-
import { analyzeResponse, defaultRepairPrompt } from "./rig.ts";
2-
import type { Agent, AgentAddon, AgentAddonContext } from "./rig.ts";
3-
4-
const DEFAULT_STEERING_WARNING = "You are running out of turns. This is your final attempt before reaching the turn limit. Please correct your output now.";
5-
6-
export type SteeringOptions = {
7-
/** Warning appended to the final retry prompt. */
8-
message?: string;
9-
};
10-
11-
export type TimeoutOptions = {
12-
timeout: number;
13-
};
14-
15-
export type AgentRegistration = (
16-
agent: Agent,
17-
context: AgentAddonContext,
18-
) => void | Promise<void>;
19-
20-
/**
21-
* Appends a final-attempt warning to the retry prompt produced by an inner addon.
22-
*
23-
* Place this before `repair()`, for example
24-
* `addons: [steering({ message: "Return valid JSON now." }), repair()]`.
25-
*/
26-
export function steering(options: SteeringOptions = {}): AgentAddon {
27-
const message = options.message ?? DEFAULT_STEERING_WARNING;
28-
return async (context, next) => {
29-
await next();
30-
if (context.nextPrompt && context.turn + 1 === context.maxTurns) {
31-
context.nextPrompt = `${context.nextPrompt}\n${message}`;
32-
}
33-
};
34-
}
35-
36-
/**
37-
* Parses and validates responses, retrying failures within the agent's turn budget.
38-
*
39-
* Configure `maxTurns` on the agent spec.
40-
*/
41-
export function repair(): AgentAddon {
42-
return async (context, next) => {
43-
await next();
44-
if (context.completed || context.error !== undefined || context.nextPrompt !== undefined) {
45-
return;
46-
}
47-
if (context.response === undefined) {
48-
return;
49-
}
50-
const analysis = analyzeResponse(context.response, context.outputSchema, context.spec.name, context.turn);
51-
if (analysis.ok) {
52-
context.completed = true;
53-
context.output = analysis.output;
54-
return;
55-
}
56-
if (context.turn >= context.maxTurns) {
57-
context.error = analysis.error;
58-
return;
59-
}
60-
context.nextPrompt = defaultRepairPrompt(context.spec, analysis.error);
61-
};
62-
}
63-
64-
export function timeout(options: TimeoutOptions): AgentAddon {
65-
return async (context, next) => {
66-
context.signal = timeoutSignal(context.signal, options.timeout);
67-
await next();
68-
};
69-
}
70-
71-
export function oncePerAgent(register: AgentRegistration): AgentAddon {
72-
const seen = new WeakSet<Agent>();
73-
return async (context, next) => {
74-
if (!seen.has(context.agent)) {
75-
await register(context.agent, context);
76-
seen.add(context.agent);
77-
}
78-
await next();
79-
};
80-
}
81-
82-
function timeoutSignal(parent?: AbortSignal, timeoutMs?: number): AbortSignal | undefined {
83-
if (!timeoutMs) {
84-
return parent;
85-
}
86-
const controller = new AbortController();
87-
const onAbort = () => controller.abort(parent?.reason);
88-
parent?.addEventListener("abort", onAbort, { once: true });
89-
const timer = setTimeout(
90-
() => controller.abort(new Error(`Timed out after ${timeoutMs}ms`)),
91-
timeoutMs,
92-
);
93-
controller.signal.addEventListener("abort", () => clearTimeout(timer), { once: true });
94-
return controller.signal;
95-
}
96-
97-
export const addons = {
98-
oncePerAgent,
99-
timeout,
100-
repair,
101-
steering,
102-
};
1+
export { addons, oncePerAgent, repair, steering, timeout } from "./rig.ts";
2+
export type { AgentRegistration, SteeringOptions, TimeoutOptions } from "./rig.ts";

skills/rig/references/agent-api.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,6 @@ Use only the current API:
219219
- `agent({ name, ... })`
220220
- `p.*` and ``p`...` `` from `rig`
221221
- `s.*` for explicit schemas
222-
- `oncePerAgent`, `repair()`, `steering`, and `timeout` from `rig/addons`
222+
- `oncePerAgent`, `repair()`, `steering`, and `timeout` from `rig`
223223

224224
Do not add deprecated hooks, alternate schema syntaxes, or compatibility bridges.

skills/rig/rig.ts

Lines changed: 70 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,6 @@ import { promisify } from "node:util";
8383
import { CopilotClient, RuntimeConnection, approveAll } from "@github/copilot-sdk";
8484
import type { CopilotClientOptions } from "@github/copilot-sdk";
8585

86-
export { addons, oncePerAgent, repair, steering, timeout } from "./addons.ts";
87-
export type { AgentRegistration, SteeringOptions, TimeoutOptions } from "./addons.ts";
88-
8986
export type Json = null | boolean | number | string | Json[] | { [key: string]: Json };
9087
export type ValidationResult = { ok: true } | { ok: false; error: string };
9188

@@ -644,6 +641,16 @@ export type AgentAddon = (
644641
context: AgentAddonContext,
645642
next: () => Promise<void>,
646643
) => void | Promise<void>;
644+
export type SteeringOptions = {
645+
message?: string;
646+
};
647+
export type TimeoutOptions = {
648+
timeout: number;
649+
};
650+
export type AgentRegistration = (
651+
agent: Agent,
652+
context: AgentAddonContext,
653+
) => void | Promise<void>;
647654
export type ToolHandler<TArgs = unknown> = (args: TArgs) => unknown | Promise<unknown>;
648655
export type ToolParameters = Schema | Record<string, unknown>;
649656
export type Tool<TArgs = unknown> = ToolConfig<TArgs> & { name: string };
@@ -1259,6 +1266,66 @@ export class AgentError extends Error {
12591266
}
12601267
}
12611268

1269+
const DEFAULT_STEERING_WARNING = "You are running out of turns. This is your final attempt before reaching the turn limit. Please correct your output now.";
1270+
1271+
export function steering(options: SteeringOptions = {}): AgentAddon {
1272+
const message = options.message ?? DEFAULT_STEERING_WARNING;
1273+
return async (context, next) => {
1274+
await next();
1275+
if (context.nextPrompt && context.turn + 1 === context.maxTurns) {
1276+
context.nextPrompt = `${context.nextPrompt}\n${message}`;
1277+
}
1278+
};
1279+
}
1280+
1281+
export function repair(): AgentAddon {
1282+
return async (context, next) => {
1283+
await next();
1284+
if (context.completed || context.error !== undefined || context.nextPrompt !== undefined) {
1285+
return;
1286+
}
1287+
if (context.response === undefined) {
1288+
return;
1289+
}
1290+
const analysis = analyzeResponse(context.response, context.outputSchema, context.spec.name, context.turn);
1291+
if (analysis.ok) {
1292+
context.completed = true;
1293+
context.output = analysis.output;
1294+
return;
1295+
}
1296+
if (context.turn >= context.maxTurns) {
1297+
context.error = analysis.error;
1298+
return;
1299+
}
1300+
context.nextPrompt = defaultRepairPrompt(context.spec, analysis.error);
1301+
};
1302+
}
1303+
1304+
export function timeout(options: TimeoutOptions): AgentAddon {
1305+
return async (context, next) => {
1306+
context.signal = timeoutSignal(context.signal, options.timeout);
1307+
await next();
1308+
};
1309+
}
1310+
1311+
export function oncePerAgent(register: AgentRegistration): AgentAddon {
1312+
const seen = new WeakSet<Agent>();
1313+
return async (context, next) => {
1314+
if (!seen.has(context.agent)) {
1315+
await register(context.agent, context);
1316+
seen.add(context.agent);
1317+
}
1318+
await next();
1319+
};
1320+
}
1321+
1322+
export const addons = {
1323+
oncePerAgent,
1324+
timeout,
1325+
repair,
1326+
steering,
1327+
};
1328+
12621329
let currentAgentFactory: AgentFactory = defaultAgentFactory();
12631330

12641331
/**

skills/rig/samples/100-workspace-config-drift-2.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
# 100 - Workspace Config Drift 2
22

33
```rig
4-
import { agent, p, s, defineTool } from "rig";
5-
import { repair } from "rig/addons";
4+
import { agent, p, s, defineTool, repair } from "rig";
65
76
const parseJson = defineTool("parseJson", {
87
description: "Parse a JSON string and return parsed object or error",

skills/rig/samples/101-commit-format-suggester-2.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
# 101 - Commit Format Suggester 2
22

33
```rig
4-
import { agent, p, s } from "rig";
5-
import { repair, steering } from "rig/addons";
4+
import { agent, p, s, repair, steering } from "rig";
65
76
// Agent role: suggest conventional commit format for recent git commit messages.
87
const commitFormatSuggester = agent({

skills/rig/samples/103-hotspot-file-analyzer-2.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
# 103 - Hotspot File Analyzer 2
22

33
```rig
4-
import { agent, p, s } from "rig";
5-
import { steering } from "rig/addons";
4+
import { agent, p, s, steering } from "rig";
65
76
// Agent role: identify hot-spot files by measuring commit churn and contributor spread.
87
const hotspotFileAnalyzer = agent({

skills/rig/samples/105-import-cycle-detector-2.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
# 105 - Import Cycle Detector 2
22

33
```rig
4-
import { agent, p, s } from "rig";
5-
import { repair } from "rig/addons";
4+
import { agent, p, s, repair } from "rig";
65
76
// Agent role: detect circular import cycles in TypeScript source files.
87
const importCycleDetector = agent({

skills/rig/samples/107-git-rename-tracker.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
# 107 - Git Rename Tracker
22

33
```rig
4-
import { agent, p, s } from "rig";
5-
import { repair } from "rig/addons";
4+
import { agent, p, s, repair } from "rig";
65
76
// Agent role: track file renames in git history and produce a structured rename log.
87
const gitRenameTracker = agent({

skills/rig/samples/110-workspace-config-drift-3.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
# 110 - Workspace Config Drift 3
22

33
```rig
4-
import { agent, defineTool, p, s } from "rig";
5-
import { repair } from "rig/addons";
4+
import { agent, defineTool, p, s, repair } from "rig";
65
76
// Agent role: detect drift in workspace config files against a baseline.
87
const workspaceConfigDrift = agent({

0 commit comments

Comments
 (0)