@poncho-ai/harness 0.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.
@@ -0,0 +1,108 @@
1
+ import type { AgentEvent } from "@poncho-ai/sdk";
2
+
3
+ export interface TelemetryConfig {
4
+ enabled?: boolean;
5
+ otlp?: string;
6
+ latitude?: {
7
+ apiKey?: string;
8
+ projectId?: string | number;
9
+ path?: string;
10
+ documentPath?: string;
11
+ };
12
+ handler?: (event: AgentEvent) => Promise<void> | void;
13
+ }
14
+
15
+ export class TelemetryEmitter {
16
+ private readonly config: TelemetryConfig | undefined;
17
+
18
+ constructor(config?: TelemetryConfig) {
19
+ this.config = config;
20
+ }
21
+
22
+ async emit(event: AgentEvent): Promise<void> {
23
+ if (this.config?.enabled === false) {
24
+ return;
25
+ }
26
+ if (this.config?.handler) {
27
+ await this.config.handler(event);
28
+ return;
29
+ }
30
+ if (this.config?.otlp) {
31
+ await this.sendOtlp(event);
32
+ }
33
+ if (this.config?.latitude?.apiKey) {
34
+ await this.sendLatitude(event);
35
+ }
36
+ // Default behavior in local dev: print concise structured logs.
37
+ process.stdout.write(`[event] ${event.type} ${JSON.stringify(event)}\n`);
38
+ }
39
+
40
+ private async sendOtlp(event: AgentEvent): Promise<void> {
41
+ const endpoint = this.config?.otlp;
42
+ if (!endpoint) {
43
+ return;
44
+ }
45
+ try {
46
+ await fetch(endpoint, {
47
+ method: "POST",
48
+ headers: { "Content-Type": "application/json" },
49
+ body: JSON.stringify({
50
+ resourceLogs: [
51
+ {
52
+ scopeLogs: [
53
+ {
54
+ logRecords: [
55
+ {
56
+ timeUnixNano: String(Date.now() * 1_000_000),
57
+ severityText: "INFO",
58
+ body: { stringValue: event.type },
59
+ attributes: [
60
+ {
61
+ key: "event.payload",
62
+ value: { stringValue: JSON.stringify(event) },
63
+ },
64
+ ],
65
+ },
66
+ ],
67
+ },
68
+ ],
69
+ },
70
+ ],
71
+ }),
72
+ });
73
+ } catch {
74
+ // Ignore telemetry delivery failures.
75
+ }
76
+ }
77
+
78
+ private async sendLatitude(event: AgentEvent): Promise<void> {
79
+ const apiKey = this.config?.latitude?.apiKey;
80
+ if (!apiKey) {
81
+ return;
82
+ }
83
+ const projectId =
84
+ this.config?.latitude?.projectId ?? process.env.LATITUDE_PROJECT_ID;
85
+ const path = this.config?.latitude?.path ?? process.env.LATITUDE_PATH;
86
+ const documentPath =
87
+ this.config?.latitude?.documentPath ?? process.env.LATITUDE_DOCUMENT_PATH;
88
+ try {
89
+ await fetch("https://api.latitude.so/v1/telemetry/events", {
90
+ method: "POST",
91
+ headers: {
92
+ "Content-Type": "application/json",
93
+ Authorization: `Bearer ${apiKey}`,
94
+ },
95
+ body: JSON.stringify({
96
+ type: event.type,
97
+ payload: event,
98
+ timestamp: Date.now(),
99
+ projectId,
100
+ path,
101
+ documentPath,
102
+ }),
103
+ });
104
+ } catch {
105
+ // Ignore telemetry delivery failures.
106
+ }
107
+ }
108
+ }
@@ -0,0 +1,69 @@
1
+ import type { ToolContext, ToolDefinition } from "@poncho-ai/sdk";
2
+
3
+ export interface ToolCall {
4
+ id: string;
5
+ name: string;
6
+ input: Record<string, unknown>;
7
+ }
8
+
9
+ export interface ToolExecutionResult {
10
+ callId: string;
11
+ tool: string;
12
+ output?: unknown;
13
+ error?: string;
14
+ }
15
+
16
+ export class ToolDispatcher {
17
+ private readonly tools = new Map<string, ToolDefinition>();
18
+
19
+ register(tool: ToolDefinition): void {
20
+ this.tools.set(tool.name, tool);
21
+ }
22
+
23
+ registerMany(tools: ToolDefinition[]): void {
24
+ for (const tool of tools) {
25
+ this.register(tool);
26
+ }
27
+ }
28
+
29
+ list(): ToolDefinition[] {
30
+ return [...this.tools.values()];
31
+ }
32
+
33
+ get(name: string): ToolDefinition | undefined {
34
+ return this.tools.get(name);
35
+ }
36
+
37
+ async execute(call: ToolCall, context: ToolContext): Promise<ToolExecutionResult> {
38
+ const definition = this.tools.get(call.name);
39
+ if (!definition) {
40
+ return {
41
+ callId: call.id,
42
+ tool: call.name,
43
+ error: `Tool not found: ${call.name}`,
44
+ };
45
+ }
46
+
47
+ try {
48
+ const output = await definition.handler(call.input, context);
49
+ return {
50
+ callId: call.id,
51
+ tool: call.name,
52
+ output,
53
+ };
54
+ } catch (error) {
55
+ return {
56
+ callId: call.id,
57
+ tool: call.name,
58
+ error: error instanceof Error ? error.message : "Unknown tool error",
59
+ };
60
+ }
61
+ }
62
+
63
+ async executeBatch(
64
+ calls: ToolCall[],
65
+ context: ToolContext,
66
+ ): Promise<ToolExecutionResult[]> {
67
+ return Promise.all(calls.map(async (call) => this.execute(call, context)));
68
+ }
69
+ }
@@ -0,0 +1,39 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { parseAgentMarkdown, renderAgentPrompt } from "../src/agent-parser.js";
3
+
4
+ describe("agent parser", () => {
5
+ it("parses frontmatter and body", () => {
6
+ const parsed = parseAgentMarkdown(`---
7
+ name: test-agent
8
+ description: test description
9
+ model:
10
+ provider: anthropic
11
+ name: claude-opus-4-5
12
+ ---
13
+
14
+ # Hello
15
+ Working dir: {{runtime.workingDir}}
16
+ `);
17
+
18
+ expect(parsed.frontmatter.name).toBe("test-agent");
19
+ expect(parsed.frontmatter.description).toBe("test description");
20
+ expect(parsed.body).toContain("# Hello");
21
+ });
22
+
23
+ it("renders mustache runtime and parameter context", () => {
24
+ const parsed = parseAgentMarkdown(`---
25
+ name: test-agent
26
+ ---
27
+
28
+ Project: {{parameters.project}}
29
+ Env: {{runtime.environment}}
30
+ `);
31
+ const prompt = renderAgentPrompt(parsed, {
32
+ parameters: { project: "poncho" },
33
+ runtime: { environment: "development", workingDir: "/tmp/work" },
34
+ });
35
+
36
+ expect(prompt).toContain("Project: poncho");
37
+ expect(prompt).toContain("Env: development");
38
+ });
39
+ });