@poncho-ai/harness 0.11.2 → 0.13.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.
- package/.turbo/turbo-build.log +5 -5
- package/CHANGELOG.md +17 -0
- package/dist/index.d.ts +60 -1
- package/dist/index.js +660 -134
- package/package.json +2 -2
- package/src/agent-parser.ts +76 -0
- package/src/config.ts +10 -0
- package/src/harness.ts +215 -24
- package/src/index.ts +1 -0
- package/src/upload-store.ts +387 -0
- package/test/agent-parser.test.ts +118 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@poncho-ai/harness",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "Agent execution runtime - conversation loop, tool dispatch, streaming",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"redis": "^5.10.0",
|
|
31
31
|
"yaml": "^2.4.0",
|
|
32
32
|
"zod": "^3.22.0",
|
|
33
|
-
"@poncho-ai/sdk": "0.
|
|
33
|
+
"@poncho-ai/sdk": "1.0.0"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"@types/mustache": "^4.2.6",
|
package/src/agent-parser.ts
CHANGED
|
@@ -22,6 +22,12 @@ export interface AgentLimitsConfig {
|
|
|
22
22
|
timeout?: number;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
export interface CronJobConfig {
|
|
26
|
+
schedule: string;
|
|
27
|
+
task: string;
|
|
28
|
+
timezone?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
25
31
|
export interface AgentFrontmatter {
|
|
26
32
|
name: string;
|
|
27
33
|
id?: string;
|
|
@@ -36,6 +42,7 @@ export interface AgentFrontmatter {
|
|
|
36
42
|
mcp?: string[];
|
|
37
43
|
scripts?: string[];
|
|
38
44
|
};
|
|
45
|
+
cron?: Record<string, CronJobConfig>;
|
|
39
46
|
}
|
|
40
47
|
|
|
41
48
|
export interface ParsedAgent {
|
|
@@ -63,6 +70,74 @@ const asRecord = (value: unknown): Record<string, unknown> =>
|
|
|
63
70
|
const asNumberOrUndefined = (value: unknown): number | undefined =>
|
|
64
71
|
typeof value === "number" ? value : undefined;
|
|
65
72
|
|
|
73
|
+
const CRON_EXPRESSION_PATTERN = /^(\S+\s+){4}\S+$/;
|
|
74
|
+
|
|
75
|
+
const validateCronExpression = (expr: string, path: string): void => {
|
|
76
|
+
if (!CRON_EXPRESSION_PATTERN.test(expr.trim())) {
|
|
77
|
+
throw new Error(
|
|
78
|
+
`Invalid cron expression at ${path}: "${expr}". Expected 5-field cron format (minute hour day month weekday).`,
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const KNOWN_TIMEZONES: Set<string> | null = (() => {
|
|
84
|
+
try {
|
|
85
|
+
return new Set(Intl.supportedValuesOf("timeZone"));
|
|
86
|
+
} catch {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
})();
|
|
90
|
+
|
|
91
|
+
const validateTimezone = (tz: string, path: string): void => {
|
|
92
|
+
if (KNOWN_TIMEZONES && !KNOWN_TIMEZONES.has(tz)) {
|
|
93
|
+
throw new Error(
|
|
94
|
+
`Invalid timezone at ${path}: "${tz}". Expected an IANA timezone string (e.g. "America/New_York", "UTC").`,
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const parseCronJobs = (
|
|
100
|
+
value: unknown,
|
|
101
|
+
): Record<string, CronJobConfig> | undefined => {
|
|
102
|
+
const raw = asRecord(value);
|
|
103
|
+
const keys = Object.keys(raw);
|
|
104
|
+
if (keys.length === 0) return undefined;
|
|
105
|
+
|
|
106
|
+
const jobs: Record<string, CronJobConfig> = {};
|
|
107
|
+
for (const jobName of keys) {
|
|
108
|
+
const jobValue = asRecord(raw[jobName]);
|
|
109
|
+
const path = `AGENT.md frontmatter cron.${jobName}`;
|
|
110
|
+
|
|
111
|
+
if (typeof jobValue.schedule !== "string" || jobValue.schedule.trim() === "") {
|
|
112
|
+
throw new Error(
|
|
113
|
+
`Invalid ${path}: "schedule" is required and must be a non-empty string.`,
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
if (typeof jobValue.task !== "string" || jobValue.task.trim() === "") {
|
|
117
|
+
throw new Error(
|
|
118
|
+
`Invalid ${path}: "task" is required and must be a non-empty string.`,
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
validateCronExpression(jobValue.schedule, path);
|
|
123
|
+
|
|
124
|
+
const timezone =
|
|
125
|
+
typeof jobValue.timezone === "string" && jobValue.timezone.trim()
|
|
126
|
+
? jobValue.timezone.trim()
|
|
127
|
+
: undefined;
|
|
128
|
+
if (timezone) {
|
|
129
|
+
validateTimezone(timezone, path);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
jobs[jobName] = {
|
|
133
|
+
schedule: jobValue.schedule.trim(),
|
|
134
|
+
task: jobValue.task,
|
|
135
|
+
timezone,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
return jobs;
|
|
139
|
+
};
|
|
140
|
+
|
|
66
141
|
export const parseAgentMarkdown = (content: string): ParsedAgent => {
|
|
67
142
|
const match = content.match(FRONTMATTER_PATTERN);
|
|
68
143
|
|
|
@@ -175,6 +250,7 @@ export const parseAgentMarkdown = (content: string): ParsedAgent => {
|
|
|
175
250
|
: undefined,
|
|
176
251
|
}
|
|
177
252
|
: undefined,
|
|
253
|
+
cron: parseCronJobs(parsed.cron),
|
|
178
254
|
};
|
|
179
255
|
|
|
180
256
|
return {
|
package/src/config.ts
CHANGED
|
@@ -23,6 +23,15 @@ export interface StorageConfig {
|
|
|
23
23
|
};
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
export interface UploadsConfig {
|
|
27
|
+
provider?: "local" | "vercel-blob" | "s3";
|
|
28
|
+
/** Vercel Blob access mode. Must match the store's configuration. Defaults to "public". */
|
|
29
|
+
access?: "public" | "private";
|
|
30
|
+
bucket?: string;
|
|
31
|
+
region?: string;
|
|
32
|
+
endpoint?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
26
35
|
export type BuiltInToolToggles = {
|
|
27
36
|
list_directory?: boolean;
|
|
28
37
|
read_file?: boolean;
|
|
@@ -67,6 +76,7 @@ export interface PonchoConfig extends McpConfig {
|
|
|
67
76
|
/** Extra directories (relative to project root) to scan for skills.
|
|
68
77
|
* `skills/` and `.poncho/skills/` are always scanned. */
|
|
69
78
|
skillPaths?: string[];
|
|
79
|
+
uploads?: UploadsConfig;
|
|
70
80
|
build?: {
|
|
71
81
|
vercel?: Record<string, unknown>;
|
|
72
82
|
docker?: Record<string, unknown>;
|
package/src/harness.ts
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import type {
|
|
3
3
|
AgentEvent,
|
|
4
|
+
ContentPart,
|
|
5
|
+
FileContentPart,
|
|
4
6
|
Message,
|
|
5
7
|
RunInput,
|
|
6
8
|
RunResult,
|
|
9
|
+
TextContentPart,
|
|
7
10
|
ToolContext,
|
|
8
11
|
ToolDefinition,
|
|
9
12
|
} from "@poncho-ai/sdk";
|
|
10
|
-
import {
|
|
13
|
+
import { getTextContent } from "@poncho-ai/sdk";
|
|
14
|
+
import type { UploadStore } from "./upload-store.js";
|
|
15
|
+
import { PONCHO_UPLOAD_SCHEME, deriveUploadKey } from "./upload-store.js";
|
|
16
|
+
import { parseAgentFile, renderAgentPrompt, type ParsedAgent, type AgentFrontmatter } from "./agent-parser.js";
|
|
11
17
|
import { loadPonchoConfig, resolveMemoryConfig, type PonchoConfig } from "./config.js";
|
|
12
18
|
import { createDefaultTools, createWriteTool } from "./default-tools.js";
|
|
13
19
|
import {
|
|
@@ -44,6 +50,7 @@ export interface HarnessOptions {
|
|
|
44
50
|
approvalId: string;
|
|
45
51
|
}) => Promise<boolean> | boolean;
|
|
46
52
|
modelProvider?: ModelProviderFactory;
|
|
53
|
+
uploadStore?: UploadStore;
|
|
47
54
|
}
|
|
48
55
|
|
|
49
56
|
export interface HarnessRunOutput {
|
|
@@ -229,6 +236,25 @@ You can extend your own capabilities by creating custom JavaScript/TypeScript sc
|
|
|
229
236
|
- Script entries outside \`./scripts/\` must also appear in \`allowed-tools\`.
|
|
230
237
|
- Keep MCP server connection details (\`url\`, auth env vars) in \`poncho.config.js\` only.
|
|
231
238
|
|
|
239
|
+
## Cron Jobs
|
|
240
|
+
|
|
241
|
+
Users can define scheduled tasks in \`AGENT.md\` frontmatter:
|
|
242
|
+
|
|
243
|
+
\`\`\`yaml
|
|
244
|
+
cron:
|
|
245
|
+
daily-report:
|
|
246
|
+
schedule: "0 9 * * *" # Standard 5-field cron expression
|
|
247
|
+
timezone: "America/New_York" # Optional IANA timezone (default: UTC)
|
|
248
|
+
task: "Generate the daily sales report"
|
|
249
|
+
\`\`\`
|
|
250
|
+
|
|
251
|
+
- Each cron job triggers an autonomous agent run with the specified task, creating a fresh conversation.
|
|
252
|
+
- In \`poncho dev\`, jobs run via an in-process scheduler and appear in the web UI sidebar (prefixed with \`[cron]\`).
|
|
253
|
+
- For Vercel: \`poncho build vercel\` generates \`vercel.json\` cron entries. Set \`CRON_SECRET\` = \`PONCHO_AUTH_TOKEN\`.
|
|
254
|
+
- Jobs can also be triggered manually: \`GET /api/cron/<jobName>\`.
|
|
255
|
+
- To carry context across cron runs, enable memory.
|
|
256
|
+
- **IMPORTANT**: When adding a new cron job, always PRESERVE all existing cron jobs. Never remove or overwrite existing jobs unless the user explicitly asks you to replace or delete them. Read the full current \`cron:\` block before editing, and append the new job alongside the existing ones.
|
|
257
|
+
|
|
232
258
|
## When users ask about customization:
|
|
233
259
|
|
|
234
260
|
- Explain and edit \`poncho.config.js\` for model/provider, storage+memory, auth, telemetry, and MCP settings.
|
|
@@ -257,6 +283,7 @@ export class AgentHarness {
|
|
|
257
283
|
private readonly modelProviderInjected: boolean;
|
|
258
284
|
private readonly dispatcher = new ToolDispatcher();
|
|
259
285
|
private readonly approvalHandler?: HarnessOptions["approvalHandler"];
|
|
286
|
+
readonly uploadStore?: UploadStore;
|
|
260
287
|
private skillContextWindow = "";
|
|
261
288
|
private memoryStore?: MemoryStore;
|
|
262
289
|
private loadedConfig?: PonchoConfig;
|
|
@@ -329,12 +356,17 @@ export class AgentHarness {
|
|
|
329
356
|
this.modelProviderInjected = !!options.modelProvider;
|
|
330
357
|
this.modelProvider = options.modelProvider ?? createModelProvider("anthropic");
|
|
331
358
|
this.approvalHandler = options.approvalHandler;
|
|
359
|
+
this.uploadStore = options.uploadStore;
|
|
332
360
|
|
|
333
361
|
if (options.toolDefinitions?.length) {
|
|
334
362
|
this.dispatcher.registerMany(options.toolDefinitions);
|
|
335
363
|
}
|
|
336
364
|
}
|
|
337
365
|
|
|
366
|
+
get frontmatter(): AgentFrontmatter | undefined {
|
|
367
|
+
return this.parsedAgent?.frontmatter;
|
|
368
|
+
}
|
|
369
|
+
|
|
338
370
|
private listActiveSkills(): string[] {
|
|
339
371
|
return [...this.activeSkillNames].sort();
|
|
340
372
|
}
|
|
@@ -706,6 +738,10 @@ export class AgentHarness {
|
|
|
706
738
|
const start = now();
|
|
707
739
|
const maxSteps = agent.frontmatter.limits?.maxSteps ?? 50;
|
|
708
740
|
const timeoutMs = (agent.frontmatter.limits?.timeout ?? 300) * 1000;
|
|
741
|
+
const platformMaxDurationSec = Number(process.env.PONCHO_MAX_DURATION) || 0;
|
|
742
|
+
const softDeadlineMs = platformMaxDurationSec > 0
|
|
743
|
+
? platformMaxDurationSec * 800
|
|
744
|
+
: 0;
|
|
709
745
|
const messages: Message[] = [...(input.messages ?? [])];
|
|
710
746
|
const events: AgentEvent[] = [];
|
|
711
747
|
|
|
@@ -763,11 +799,42 @@ ${boundedMainMemory.trim()}`
|
|
|
763
799
|
agentId: agent.frontmatter.id ?? agent.frontmatter.name,
|
|
764
800
|
});
|
|
765
801
|
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
802
|
+
if (input.files && input.files.length > 0) {
|
|
803
|
+
const parts: ContentPart[] = [
|
|
804
|
+
{ type: "text", text: input.task } satisfies TextContentPart,
|
|
805
|
+
];
|
|
806
|
+
for (const file of input.files) {
|
|
807
|
+
if (this.uploadStore) {
|
|
808
|
+
const buf = Buffer.from(file.data, "base64");
|
|
809
|
+
const key = deriveUploadKey(buf, file.mediaType);
|
|
810
|
+
const ref = await this.uploadStore.put(key, buf, file.mediaType);
|
|
811
|
+
parts.push({
|
|
812
|
+
type: "file",
|
|
813
|
+
data: ref,
|
|
814
|
+
mediaType: file.mediaType,
|
|
815
|
+
filename: file.filename,
|
|
816
|
+
} satisfies FileContentPart);
|
|
817
|
+
} else {
|
|
818
|
+
parts.push({
|
|
819
|
+
type: "file",
|
|
820
|
+
data: file.data,
|
|
821
|
+
mediaType: file.mediaType,
|
|
822
|
+
filename: file.filename,
|
|
823
|
+
} satisfies FileContentPart);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
messages.push({
|
|
827
|
+
role: "user",
|
|
828
|
+
content: parts,
|
|
829
|
+
metadata: { timestamp: now(), id: randomUUID() },
|
|
830
|
+
});
|
|
831
|
+
} else {
|
|
832
|
+
messages.push({
|
|
833
|
+
role: "user",
|
|
834
|
+
content: input.task,
|
|
835
|
+
metadata: { timestamp: now(), id: randomUUID() },
|
|
836
|
+
});
|
|
837
|
+
}
|
|
771
838
|
|
|
772
839
|
let responseText = "";
|
|
773
840
|
let totalInputTokens = 0;
|
|
@@ -791,6 +858,19 @@ ${boundedMainMemory.trim()}`
|
|
|
791
858
|
});
|
|
792
859
|
return;
|
|
793
860
|
}
|
|
861
|
+
if (softDeadlineMs > 0 && now() - start > softDeadlineMs) {
|
|
862
|
+
const result: RunResult = {
|
|
863
|
+
status: "completed",
|
|
864
|
+
response: responseText,
|
|
865
|
+
steps: step - 1,
|
|
866
|
+
tokens: { input: totalInputTokens, output: totalOutputTokens, cached: 0 },
|
|
867
|
+
duration: now() - start,
|
|
868
|
+
continuation: true,
|
|
869
|
+
maxSteps,
|
|
870
|
+
};
|
|
871
|
+
yield pushEvent({ type: "run:completed", runId, result });
|
|
872
|
+
return;
|
|
873
|
+
}
|
|
794
874
|
|
|
795
875
|
const stepStart = now();
|
|
796
876
|
yield pushEvent({ type: "step:started", step });
|
|
@@ -818,13 +898,13 @@ ${boundedMainMemory.trim()}`
|
|
|
818
898
|
}
|
|
819
899
|
|
|
820
900
|
// Convert messages to ModelMessage format
|
|
821
|
-
const
|
|
822
|
-
(msg): ModelMessage[] => {
|
|
901
|
+
const convertMessage = async (msg: Message): Promise<ModelMessage[]> => {
|
|
823
902
|
if (msg.role === "tool") {
|
|
824
903
|
// Tool messages are provider-sensitive; skip malformed historical records
|
|
825
904
|
// instead of failing the entire run continuation.
|
|
905
|
+
const textContent = typeof msg.content === "string" ? msg.content : getTextContent(msg);
|
|
826
906
|
try {
|
|
827
|
-
const parsed: unknown = JSON.parse(
|
|
907
|
+
const parsed: unknown = JSON.parse(textContent);
|
|
828
908
|
if (!Array.isArray(parsed)) {
|
|
829
909
|
return [];
|
|
830
910
|
}
|
|
@@ -881,12 +961,13 @@ ${boundedMainMemory.trim()}`
|
|
|
881
961
|
if (msg.role === "assistant") {
|
|
882
962
|
// Assistant messages may contain serialized tool calls from previous runs.
|
|
883
963
|
// Keep only valid tool-call records to avoid broken continuation payloads.
|
|
964
|
+
const assistantText = typeof msg.content === "string" ? msg.content : getTextContent(msg);
|
|
884
965
|
try {
|
|
885
|
-
const parsed: unknown = JSON.parse(
|
|
966
|
+
const parsed: unknown = JSON.parse(assistantText);
|
|
886
967
|
if (typeof parsed === "object" && parsed !== null) {
|
|
887
968
|
const parsedRecord = parsed as { text?: unknown; tool_calls?: unknown };
|
|
888
969
|
if (!Array.isArray(parsedRecord.tool_calls)) {
|
|
889
|
-
return [{ role: "assistant" as const, content:
|
|
970
|
+
return [{ role: "assistant" as const, content: assistantText }];
|
|
890
971
|
}
|
|
891
972
|
const textPart = typeof parsedRecord.text === "string" ? parsedRecord.text : "";
|
|
892
973
|
const validToolCalls = parsedRecord.tool_calls
|
|
@@ -922,18 +1003,115 @@ ${boundedMainMemory.trim()}`
|
|
|
922
1003
|
} catch {
|
|
923
1004
|
// Not JSON, treat as regular assistant text.
|
|
924
1005
|
}
|
|
925
|
-
return [{ role: "assistant" as const, content:
|
|
1006
|
+
return [{ role: "assistant" as const, content: assistantText }];
|
|
926
1007
|
}
|
|
927
1008
|
|
|
928
|
-
if (msg.role === "
|
|
1009
|
+
if (msg.role === "system") {
|
|
929
1010
|
return [{
|
|
930
|
-
role:
|
|
931
|
-
content: msg.content,
|
|
1011
|
+
role: "system" as const,
|
|
1012
|
+
content: typeof msg.content === "string" ? msg.content : getTextContent(msg),
|
|
932
1013
|
}];
|
|
933
1014
|
}
|
|
934
1015
|
|
|
1016
|
+
if (msg.role === "user") {
|
|
1017
|
+
if (typeof msg.content === "string") {
|
|
1018
|
+
return [{ role: "user" as const, content: msg.content }];
|
|
1019
|
+
}
|
|
1020
|
+
// Convert ContentPart[] to Vercel AI SDK UserContent.
|
|
1021
|
+
// Only specific media types can be sent as binary attachments —
|
|
1022
|
+
// everything else is inlined as text or skipped gracefully.
|
|
1023
|
+
const MODEL_IMAGE_TYPES = new Set([
|
|
1024
|
+
"image/jpeg", "image/png", "image/gif", "image/webp",
|
|
1025
|
+
]);
|
|
1026
|
+
const MODEL_FILE_TYPES = new Set([
|
|
1027
|
+
"application/pdf",
|
|
1028
|
+
]);
|
|
1029
|
+
const isTextBasedMime = (mt: string): boolean =>
|
|
1030
|
+
mt.startsWith("text/") ||
|
|
1031
|
+
mt === "application/json" ||
|
|
1032
|
+
mt === "application/xml" ||
|
|
1033
|
+
mt === "application/x-yaml" ||
|
|
1034
|
+
mt.endsWith("+json") ||
|
|
1035
|
+
mt.endsWith("+xml");
|
|
1036
|
+
|
|
1037
|
+
const userContent = await Promise.all(
|
|
1038
|
+
msg.content.map(async (part) => {
|
|
1039
|
+
if (part.type === "text") {
|
|
1040
|
+
return { type: "text" as const, text: part.text };
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
const isSupportedImage = MODEL_IMAGE_TYPES.has(part.mediaType);
|
|
1044
|
+
const isSupportedFile = MODEL_FILE_TYPES.has(part.mediaType);
|
|
1045
|
+
|
|
1046
|
+
// Text-based files: inline the content so the model can read it
|
|
1047
|
+
if (!isSupportedImage && !isSupportedFile && isTextBasedMime(part.mediaType)) {
|
|
1048
|
+
let textContent: string;
|
|
1049
|
+
try {
|
|
1050
|
+
if (part.data.startsWith(PONCHO_UPLOAD_SCHEME) && this.uploadStore) {
|
|
1051
|
+
const buf = await this.uploadStore.get(part.data);
|
|
1052
|
+
textContent = buf.toString("utf8");
|
|
1053
|
+
} else if (part.data.startsWith("https://") || part.data.startsWith("http://")) {
|
|
1054
|
+
const resp = await fetch(part.data);
|
|
1055
|
+
textContent = await resp.text();
|
|
1056
|
+
} else {
|
|
1057
|
+
textContent = Buffer.from(part.data, "base64").toString("utf8");
|
|
1058
|
+
}
|
|
1059
|
+
} catch {
|
|
1060
|
+
textContent = "(could not read file)";
|
|
1061
|
+
}
|
|
1062
|
+
const label = part.filename ?? part.mediaType;
|
|
1063
|
+
return { type: "text" as const, text: `[File: ${label}]\n${textContent}` };
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
// Unsupported binary formats (e.g. AVIF, HEIC, MP4): skip with a note
|
|
1067
|
+
if (!isSupportedImage && !isSupportedFile) {
|
|
1068
|
+
const label = part.filename ?? part.mediaType;
|
|
1069
|
+
return {
|
|
1070
|
+
type: "text" as const,
|
|
1071
|
+
text: `[Attached file: ${label} (${part.mediaType}) — this format is not supported by the model and was skipped]`,
|
|
1072
|
+
};
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
// Always resolve to base64 so the model doesn't need to
|
|
1076
|
+
// fetch URLs itself (which fails for private blob stores).
|
|
1077
|
+
let resolvedData: string;
|
|
1078
|
+
if (part.data.startsWith(PONCHO_UPLOAD_SCHEME) && this.uploadStore) {
|
|
1079
|
+
const buf = await this.uploadStore.get(part.data);
|
|
1080
|
+
resolvedData = buf.toString("base64");
|
|
1081
|
+
} else if (part.data.startsWith("https://") || part.data.startsWith("http://")) {
|
|
1082
|
+
if (this.uploadStore) {
|
|
1083
|
+
const buf = await this.uploadStore.get(part.data);
|
|
1084
|
+
resolvedData = buf.toString("base64");
|
|
1085
|
+
} else {
|
|
1086
|
+
const resp = await fetch(part.data);
|
|
1087
|
+
resolvedData = Buffer.from(await resp.arrayBuffer()).toString("base64");
|
|
1088
|
+
}
|
|
1089
|
+
} else {
|
|
1090
|
+
resolvedData = part.data;
|
|
1091
|
+
}
|
|
1092
|
+
if (isSupportedImage) {
|
|
1093
|
+
return {
|
|
1094
|
+
type: "image" as const,
|
|
1095
|
+
image: resolvedData,
|
|
1096
|
+
mediaType: part.mediaType,
|
|
1097
|
+
};
|
|
1098
|
+
}
|
|
1099
|
+
return {
|
|
1100
|
+
type: "file" as const,
|
|
1101
|
+
data: resolvedData,
|
|
1102
|
+
mediaType: part.mediaType,
|
|
1103
|
+
filename: part.filename,
|
|
1104
|
+
};
|
|
1105
|
+
}),
|
|
1106
|
+
);
|
|
1107
|
+
return [{ role: "user" as const, content: userContent }];
|
|
1108
|
+
}
|
|
1109
|
+
|
|
935
1110
|
return [];
|
|
936
|
-
}
|
|
1111
|
+
};
|
|
1112
|
+
const coreMessages: ModelMessage[] = (
|
|
1113
|
+
await Promise.all(trimMessageWindow(messages).map(convertMessage))
|
|
1114
|
+
).flat();
|
|
937
1115
|
|
|
938
1116
|
const modelName = agent.frontmatter.model?.name ?? "claude-opus-4-5";
|
|
939
1117
|
const temperature = agent.frontmatter.model?.temperature ?? 0.2;
|
|
@@ -1319,14 +1497,27 @@ ${boundedMainMemory.trim()}`
|
|
|
1319
1497
|
}
|
|
1320
1498
|
}
|
|
1321
1499
|
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1500
|
+
if (softDeadlineMs > 0) {
|
|
1501
|
+
const result: RunResult = {
|
|
1502
|
+
status: "completed",
|
|
1503
|
+
response: responseText,
|
|
1504
|
+
steps: maxSteps,
|
|
1505
|
+
tokens: { input: totalInputTokens, output: totalOutputTokens, cached: 0 },
|
|
1506
|
+
duration: now() - start,
|
|
1507
|
+
continuation: true,
|
|
1508
|
+
maxSteps,
|
|
1509
|
+
};
|
|
1510
|
+
yield pushEvent({ type: "run:completed", runId, result });
|
|
1511
|
+
} else {
|
|
1512
|
+
yield pushEvent({
|
|
1513
|
+
type: "run:error",
|
|
1514
|
+
runId,
|
|
1515
|
+
error: {
|
|
1516
|
+
code: "MAX_STEPS_EXCEEDED",
|
|
1517
|
+
message: `Run reached maximum of ${maxSteps} steps`,
|
|
1518
|
+
},
|
|
1519
|
+
});
|
|
1520
|
+
}
|
|
1330
1521
|
}
|
|
1331
1522
|
|
|
1332
1523
|
async runToCompletion(input: RunInput): Promise<HarnessRunOutput> {
|
package/src/index.ts
CHANGED
|
@@ -11,6 +11,7 @@ export * from "./schema-converter.js";
|
|
|
11
11
|
export * from "./skill-context.js";
|
|
12
12
|
export * from "./skill-tools.js";
|
|
13
13
|
export * from "./state.js";
|
|
14
|
+
export * from "./upload-store.js";
|
|
14
15
|
export * from "./telemetry.js";
|
|
15
16
|
export * from "./tool-dispatcher.js";
|
|
16
17
|
export { defineTool } from "@poncho-ai/sdk";
|