Skip to content

Commit 709febe

Browse files
authored
Add shared engine.driver field with @earendil-works/pi-agent-core built-in driver (#40897)
1 parent c4d2540 commit 709febe

26 files changed

Lines changed: 1041 additions & 153 deletions

.changeset/minor-pi-agent-core-driver.md

Lines changed: 36 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
#!/usr/bin/env node
2+
// @ts-check
3+
"use strict";
4+
5+
/**
6+
* Pi Agent Core Driver Sample
7+
*
8+
* This is a minimal sample of a pi-agent-core driver that can be customised
9+
* and placed in .github/drivers/ in your repository. Set it as the engine
10+
* driver in your workflow frontmatter:
11+
*
12+
* engine:
13+
* id: pi
14+
* driver: .github/drivers/pi_agent_core_driver_sample_node.cjs
15+
*
16+
* The driver uses @earendil-works/pi-agent-core to run a pi agent session
17+
* directly from Node.js and writes a JSONL log that gh-aw's parser understands.
18+
*
19+
* For the full implementation see:
20+
* actions/setup/js/pi_agent_core_driver.cjs
21+
*
22+
* See also:
23+
* https://github.com/earendil-works/pi/blob/main/packages/agent/README.md
24+
*/
25+
26+
const { execSync } = require("child_process");
27+
const fs = require("fs");
28+
const crypto = require("crypto");
29+
30+
// ---------------------------------------------------------------------------
31+
// Minimal JSONL emitter
32+
// ---------------------------------------------------------------------------
33+
34+
/** @param {unknown} obj */
35+
function emitJsonl(obj) {
36+
process.stdout.write(JSON.stringify(obj) + "\n");
37+
}
38+
39+
// ---------------------------------------------------------------------------
40+
// API key resolution (customise as needed)
41+
// ---------------------------------------------------------------------------
42+
43+
/** @param {string} provider */
44+
function getApiKey(provider) {
45+
switch (provider) {
46+
case "github-copilot":
47+
case "copilot":
48+
return process.env.COPILOT_GITHUB_TOKEN || process.env.GITHUB_TOKEN;
49+
case "anthropic":
50+
return process.env.ANTHROPIC_API_KEY;
51+
case "openai":
52+
return process.env.CODEX_API_KEY || process.env.OPENAI_API_KEY;
53+
default:
54+
return undefined;
55+
}
56+
}
57+
58+
// ---------------------------------------------------------------------------
59+
// Entry point
60+
// ---------------------------------------------------------------------------
61+
62+
async function main() {
63+
// Install required packages before dynamic imports.
64+
execSync(
65+
"npm install --ignore-scripts --no-save @earendil-works/pi-agent-core @earendil-works/pi-ai",
66+
{ stdio: "inherit", cwd: process.env.GITHUB_WORKSPACE || process.cwd() }
67+
);
68+
69+
const promptFile = process.env.GH_AW_PROMPT;
70+
if (!promptFile) throw new Error("GH_AW_PROMPT is not set");
71+
const prompt = fs.readFileSync(promptFile, "utf8");
72+
73+
// Choose a model in "provider/model" format (default: GitHub Copilot).
74+
const modelStr = process.env.GH_AW_PI_MODEL || process.env.PI_MODEL || "copilot/claude-sonnet-4-20250514";
75+
const slashIdx = modelStr.indexOf("/");
76+
const providerPrefix = slashIdx > 0 ? modelStr.slice(0, slashIdx).toLowerCase() : "copilot";
77+
const modelId = slashIdx > 0 ? modelStr.slice(slashIdx + 1) : modelStr;
78+
79+
// Resolve provider and api type.
80+
let provider = "github-copilot";
81+
let api = "openai-completions";
82+
let baseUrl = "https://api.githubcopilot.com";
83+
if (providerPrefix === "anthropic") {
84+
provider = "anthropic";
85+
api = "anthropic-messages";
86+
baseUrl = "https://api.anthropic.com";
87+
}
88+
89+
/** @type {Record<string, unknown>} */
90+
const model = {
91+
id: modelId,
92+
name: modelId,
93+
api,
94+
provider,
95+
baseUrl,
96+
reasoning: false,
97+
input: ["text"],
98+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
99+
contextWindow: 200000,
100+
maxTokens: 8192,
101+
};
102+
103+
// ESM modules must be loaded with dynamic import() in a CJS file.
104+
const { Agent } = await import("@earendil-works/pi-agent-core");
105+
await import("@earendil-works/pi-ai"); // registers built-in API providers
106+
107+
const sessionId = crypto.randomUUID();
108+
const startMs = Date.now();
109+
let inputTokens = 0;
110+
let outputTokens = 0;
111+
let turns = 0;
112+
113+
const agent = new Agent({
114+
initialState: { systemPrompt: "", model },
115+
getApiKey,
116+
});
117+
118+
agent.subscribe(event => {
119+
switch (event.type) {
120+
case "agent_start":
121+
emitJsonl({ type: "init", model: modelId, session_id: sessionId });
122+
break;
123+
case "message_update":
124+
if (event.assistantMessageEvent?.type === "text_delta") {
125+
emitJsonl({ type: "assistant", content: event.assistantMessageEvent.delta, delta: true });
126+
}
127+
break;
128+
case "tool_execution_start":
129+
emitJsonl({ type: "tool_use", tool_name: event.toolName, tool_id: event.toolCallId, parameters: event.args ?? {} });
130+
break;
131+
case "tool_execution_end": {
132+
const out = typeof event.result === "string" ? event.result : event.result != null ? JSON.stringify(event.result) : "";
133+
emitJsonl({ type: "tool_result", tool_id: event.toolCallId, status: event.isError ? "error" : "success", output: out });
134+
break;
135+
}
136+
case "turn_end":
137+
turns++;
138+
if (event.message?.usage) {
139+
inputTokens += event.message.usage.input ?? 0;
140+
outputTokens += event.message.usage.output ?? 0;
141+
}
142+
break;
143+
case "agent_end":
144+
emitJsonl({ type: "result", stats: { input_tokens: inputTokens, output_tokens: outputTokens, duration_ms: Date.now() - startMs, turns } });
145+
break;
146+
}
147+
});
148+
149+
await agent.prompt(prompt);
150+
await agent.waitForIdle();
151+
}
152+
153+
if (require.main === module) {
154+
main().catch(err => {
155+
process.stderr.write(`[pi-agent-core-driver-sample] ${err instanceof Error ? err.stack : String(err)}\n`);
156+
process.exit(1);
157+
});
158+
}

.github/workflows/daily-issues-report.lock.yml

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.github/workflows/daily-issues-report.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ permissions:
1313
engine:
1414
id: copilot
1515
copilot-sdk: true
16-
copilot-sdk-driver: .github/drivers/copilot_sdk_driver_sample_python.py
16+
driver: .github/drivers/copilot_sdk_driver_sample_python.py
1717
runs-on: aw-gpu-runner-T4
1818
strict: true
1919
tracker-id: daily-issues-report
@@ -58,6 +58,7 @@ imports:
5858
features:
5959
gh-aw-detection: true
6060
---
61+
6162
{{#runtime-import? .github/shared-instructions.md}}
6263

6364
{{#runtime-import .github/shared/editorial.md}}
@@ -391,4 +392,4 @@ A successful run will:
391392

392393
Begin your analysis now. Load the data, run the Python analysis, generate charts, and create the discussion report.
393394

394-
{{#runtime-import shared/noop-reminder.md}}
395+
{{#runtime-import shared/noop-reminder.md}}

.github/workflows/daily-model-inventory.lock.yml

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.github/workflows/daily-model-inventory.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ tracker-id: daily-model-inventory
1717
engine:
1818
id: copilot
1919
copilot-sdk: true
20-
copilot-sdk-driver: .github/drivers/copilot_sdk_driver_sample_node.cjs
20+
driver: .github/drivers/copilot_sdk_driver_sample_node.cjs
2121
strict: true
2222
timeout-minutes: 30
2323

@@ -651,4 +651,4 @@ If no updates are needed (all live models are already covered by existing aliase
651651
title `Model alias inventory - no changes needed - YYYY-MM-DD` and a brief summary confirming
652652
coverage is up to date.
653653

654-
{{#runtime-import shared/noop-reminder.md}}
654+
{{#runtime-import shared/noop-reminder.md}}

.github/workflows/daily-skill-optimizer.lock.yml

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.github/workflows/daily-skill-optimizer.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ tracker-id: daily-skill-optimizer
1515
engine:
1616
id: copilot
1717
copilot-sdk: true
18-
copilot-sdk-driver: .github/drivers/copilot_sdk_driver_sample_typescript.ts
18+
driver: .github/drivers/copilot_sdk_driver_sample_typescript.ts
1919
strict: true
2020
timeout-minutes: 45
2121

@@ -222,4 +222,4 @@ Structure the issue body as follows:
222222
[Numbered list of 3 actionable improvements]
223223
```
224224

225-
Do not call `noop` for this workflow; always create exactly one issue with exactly 3 improvements.
225+
Do not call `noop` for this workflow; always create exactly one issue with exactly 3 improvements.

0 commit comments

Comments
 (0)