@poncho-ai/harness 0.25.0 → 0.26.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 +6 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +497 -286
- package/package.json +1 -1
- package/src/config.ts +4 -0
- package/src/harness.ts +18 -2
- package/src/kv-store.ts +206 -0
- package/src/memory.ts +26 -291
- package/src/todo-tools.ts +363 -0
- package/.turbo/turbo-lint.log +0 -6
- package/.turbo/turbo-test.log +0 -135
package/package.json
CHANGED
package/src/config.ts
CHANGED
|
@@ -42,6 +42,10 @@ export type BuiltInToolToggles = {
|
|
|
42
42
|
edit_file?: boolean;
|
|
43
43
|
delete_file?: boolean;
|
|
44
44
|
delete_directory?: boolean;
|
|
45
|
+
todo_list?: boolean;
|
|
46
|
+
todo_add?: boolean;
|
|
47
|
+
todo_update?: boolean;
|
|
48
|
+
todo_remove?: boolean;
|
|
45
49
|
};
|
|
46
50
|
|
|
47
51
|
export interface MessagingChannelConfig {
|
package/src/harness.ts
CHANGED
|
@@ -16,13 +16,14 @@ import { getTextContent } from "@poncho-ai/sdk";
|
|
|
16
16
|
import type { UploadStore } from "./upload-store.js";
|
|
17
17
|
import { PONCHO_UPLOAD_SCHEME, deriveUploadKey } from "./upload-store.js";
|
|
18
18
|
import { parseAgentFile, parseAgentMarkdown, renderAgentPrompt, type ParsedAgent, type AgentFrontmatter } from "./agent-parser.js";
|
|
19
|
-
import { loadPonchoConfig, resolveMemoryConfig, type PonchoConfig, type ToolAccess, type BuiltInToolToggles } from "./config.js";
|
|
19
|
+
import { loadPonchoConfig, resolveMemoryConfig, resolveStateConfig, type PonchoConfig, type ToolAccess, type BuiltInToolToggles } from "./config.js";
|
|
20
20
|
import { createDefaultTools, createDeleteDirectoryTool, createDeleteTool, createEditTool, createWriteTool, ponchoDocsTool } from "./default-tools.js";
|
|
21
21
|
import {
|
|
22
22
|
createMemoryStore,
|
|
23
23
|
createMemoryTools,
|
|
24
24
|
type MemoryStore,
|
|
25
25
|
} from "./memory.js";
|
|
26
|
+
import { createTodoStore, createTodoTools, type TodoItem, type TodoStore } from "./todo-tools.js";
|
|
26
27
|
import { LocalMcpBridge } from "./mcp.js";
|
|
27
28
|
import { createModelProvider, getModelContextWindow, type ModelProviderFactory, type ProviderConfig } from "./model-factory.js";
|
|
28
29
|
import { buildSkillContextWindow, loadSkillMetadata } from "./skill-context.js";
|
|
@@ -550,6 +551,7 @@ export class AgentHarness {
|
|
|
550
551
|
readonly uploadStore?: UploadStore;
|
|
551
552
|
private skillContextWindow = "";
|
|
552
553
|
private memoryStore?: MemoryStore;
|
|
554
|
+
private todoStore?: TodoStore;
|
|
553
555
|
private loadedConfig?: PonchoConfig;
|
|
554
556
|
private loadedSkills: SkillMetadata[] = [];
|
|
555
557
|
private skillFingerprint = "";
|
|
@@ -678,6 +680,11 @@ export class AgentHarness {
|
|
|
678
680
|
return this.parsedAgent?.frontmatter;
|
|
679
681
|
}
|
|
680
682
|
|
|
683
|
+
async getTodos(conversationId: string): Promise<TodoItem[]> {
|
|
684
|
+
if (!this.todoStore) return [];
|
|
685
|
+
return this.todoStore.get(conversationId);
|
|
686
|
+
}
|
|
687
|
+
|
|
681
688
|
private listActiveSkills(): string[] {
|
|
682
689
|
return [...this.activeSkillNames].sort();
|
|
683
690
|
}
|
|
@@ -1008,8 +1015,9 @@ export class AgentHarness {
|
|
|
1008
1015
|
this.skillContextWindow = buildSkillContextWindow(skillMetadata);
|
|
1009
1016
|
this.skillFingerprint = this.buildSkillFingerprint(skillMetadata);
|
|
1010
1017
|
this.registerSkillTools(skillMetadata);
|
|
1018
|
+
const agentId = this.parsedAgent.frontmatter.id ?? this.parsedAgent.frontmatter.name;
|
|
1019
|
+
|
|
1011
1020
|
if (memoryConfig?.enabled) {
|
|
1012
|
-
const agentId = this.parsedAgent.frontmatter.id ?? this.parsedAgent.frontmatter.name;
|
|
1013
1021
|
this.memoryStore = createMemoryStore(
|
|
1014
1022
|
agentId,
|
|
1015
1023
|
memoryConfig,
|
|
@@ -1022,6 +1030,14 @@ export class AgentHarness {
|
|
|
1022
1030
|
);
|
|
1023
1031
|
}
|
|
1024
1032
|
|
|
1033
|
+
const stateConfig = resolveStateConfig(config);
|
|
1034
|
+
this.todoStore = createTodoStore(agentId, stateConfig, { workingDir: this.workingDir });
|
|
1035
|
+
for (const tool of createTodoTools(this.todoStore)) {
|
|
1036
|
+
if (this.isToolEnabled(tool.name)) {
|
|
1037
|
+
this.registerIfMissing(tool);
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1025
1041
|
if (config?.browser) {
|
|
1026
1042
|
await this.initBrowserTools(config)
|
|
1027
1043
|
.catch((e) => {
|
package/src/kv-store.ts
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import type { StateConfig } from "./state.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Minimal raw key-value interface shared by MemoryStore, TodoStore, and any
|
|
5
|
+
* future stores that sit on top of the same user-configured backend.
|
|
6
|
+
*/
|
|
7
|
+
export interface RawKVStore {
|
|
8
|
+
get(key: string): Promise<string | undefined>;
|
|
9
|
+
set(key: string, value: string): Promise<void>;
|
|
10
|
+
setWithTtl(key: string, value: string, ttlSeconds: number): Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// Upstash
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
class UpstashKVStore implements RawKVStore {
|
|
18
|
+
private readonly baseUrl: string;
|
|
19
|
+
private readonly token: string;
|
|
20
|
+
|
|
21
|
+
constructor(baseUrl: string, token: string) {
|
|
22
|
+
this.baseUrl = baseUrl.replace(/\/+$/, "");
|
|
23
|
+
this.token = token;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
private headers(): HeadersInit {
|
|
27
|
+
return { Authorization: `Bearer ${this.token}`, "Content-Type": "application/json" };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async get(key: string): Promise<string | undefined> {
|
|
31
|
+
const response = await fetch(`${this.baseUrl}/get/${encodeURIComponent(key)}`, {
|
|
32
|
+
method: "POST",
|
|
33
|
+
headers: this.headers(),
|
|
34
|
+
});
|
|
35
|
+
if (!response.ok) return undefined;
|
|
36
|
+
const payload = (await response.json()) as { result?: string | null };
|
|
37
|
+
return payload.result ?? undefined;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async set(key: string, value: string): Promise<void> {
|
|
41
|
+
await fetch(
|
|
42
|
+
`${this.baseUrl}/set/${encodeURIComponent(key)}/${encodeURIComponent(value)}`,
|
|
43
|
+
{ method: "POST", headers: this.headers() },
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async setWithTtl(key: string, value: string, ttl: number): Promise<void> {
|
|
48
|
+
await fetch(
|
|
49
|
+
`${this.baseUrl}/setex/${encodeURIComponent(key)}/${Math.max(1, ttl)}/${encodeURIComponent(value)}`,
|
|
50
|
+
{ method: "POST", headers: this.headers() },
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
// Redis
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
|
|
59
|
+
class RedisKVStore implements RawKVStore {
|
|
60
|
+
private readonly clientPromise: Promise<
|
|
61
|
+
| {
|
|
62
|
+
get: (key: string) => Promise<string | null>;
|
|
63
|
+
set: (key: string, value: string, options?: { EX?: number }) => Promise<unknown>;
|
|
64
|
+
}
|
|
65
|
+
| undefined
|
|
66
|
+
>;
|
|
67
|
+
|
|
68
|
+
constructor(url: string) {
|
|
69
|
+
this.clientPromise = (async () => {
|
|
70
|
+
try {
|
|
71
|
+
const redisModule = (await import("redis")) as unknown as {
|
|
72
|
+
createClient: (args: { url: string }) => {
|
|
73
|
+
connect: () => Promise<unknown>;
|
|
74
|
+
get: (key: string) => Promise<string | null>;
|
|
75
|
+
set: (key: string, value: string, options?: { EX?: number }) => Promise<unknown>;
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
const client = redisModule.createClient({ url });
|
|
79
|
+
await client.connect();
|
|
80
|
+
return client;
|
|
81
|
+
} catch {
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
})();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async get(key: string): Promise<string | undefined> {
|
|
88
|
+
const client = await this.clientPromise;
|
|
89
|
+
if (!client) throw new Error("Redis unavailable");
|
|
90
|
+
const value = await client.get(key);
|
|
91
|
+
return value ?? undefined;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async set(key: string, value: string): Promise<void> {
|
|
95
|
+
const client = await this.clientPromise;
|
|
96
|
+
if (!client) throw new Error("Redis unavailable");
|
|
97
|
+
await client.set(key, value);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async setWithTtl(key: string, value: string, ttl: number): Promise<void> {
|
|
101
|
+
const client = await this.clientPromise;
|
|
102
|
+
if (!client) throw new Error("Redis unavailable");
|
|
103
|
+
await client.set(key, value, { EX: Math.max(1, ttl) });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ---------------------------------------------------------------------------
|
|
108
|
+
// DynamoDB
|
|
109
|
+
// ---------------------------------------------------------------------------
|
|
110
|
+
|
|
111
|
+
class DynamoDbKVStore implements RawKVStore {
|
|
112
|
+
private readonly table: string;
|
|
113
|
+
private readonly clientPromise: Promise<
|
|
114
|
+
| {
|
|
115
|
+
send: (command: unknown) => Promise<unknown>;
|
|
116
|
+
GetItemCommand: new (input: unknown) => unknown;
|
|
117
|
+
PutItemCommand: new (input: unknown) => unknown;
|
|
118
|
+
}
|
|
119
|
+
| undefined
|
|
120
|
+
>;
|
|
121
|
+
|
|
122
|
+
constructor(table: string, region?: string) {
|
|
123
|
+
this.table = table;
|
|
124
|
+
this.clientPromise = (async () => {
|
|
125
|
+
try {
|
|
126
|
+
const module = (await import("@aws-sdk/client-dynamodb")) as {
|
|
127
|
+
DynamoDBClient: new (input: { region?: string }) => {
|
|
128
|
+
send: (command: unknown) => Promise<unknown>;
|
|
129
|
+
};
|
|
130
|
+
GetItemCommand: new (input: unknown) => unknown;
|
|
131
|
+
PutItemCommand: new (input: unknown) => unknown;
|
|
132
|
+
};
|
|
133
|
+
const client = new module.DynamoDBClient({ region });
|
|
134
|
+
return {
|
|
135
|
+
send: client.send.bind(client),
|
|
136
|
+
GetItemCommand: module.GetItemCommand,
|
|
137
|
+
PutItemCommand: module.PutItemCommand,
|
|
138
|
+
};
|
|
139
|
+
} catch {
|
|
140
|
+
return undefined;
|
|
141
|
+
}
|
|
142
|
+
})();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async get(key: string): Promise<string | undefined> {
|
|
146
|
+
const client = await this.clientPromise;
|
|
147
|
+
if (!client) throw new Error("DynamoDB unavailable");
|
|
148
|
+
const result = (await client.send(
|
|
149
|
+
new client.GetItemCommand({ TableName: this.table, Key: { runId: { S: key } } }),
|
|
150
|
+
)) as { Item?: { value?: { S?: string } } };
|
|
151
|
+
return result.Item?.value?.S;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async set(key: string, value: string): Promise<void> {
|
|
155
|
+
const client = await this.clientPromise;
|
|
156
|
+
if (!client) throw new Error("DynamoDB unavailable");
|
|
157
|
+
await client.send(
|
|
158
|
+
new client.PutItemCommand({
|
|
159
|
+
TableName: this.table,
|
|
160
|
+
Item: { runId: { S: key }, value: { S: value } },
|
|
161
|
+
}),
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async setWithTtl(key: string, value: string, ttl: number): Promise<void> {
|
|
166
|
+
const client = await this.clientPromise;
|
|
167
|
+
if (!client) throw new Error("DynamoDB unavailable");
|
|
168
|
+
const ttlEpoch = Math.floor(Date.now() / 1000) + Math.max(1, ttl);
|
|
169
|
+
await client.send(
|
|
170
|
+
new client.PutItemCommand({
|
|
171
|
+
TableName: this.table,
|
|
172
|
+
Item: { runId: { S: key }, value: { S: value }, ttl: { N: String(ttlEpoch) } },
|
|
173
|
+
}),
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// ---------------------------------------------------------------------------
|
|
179
|
+
// Factory — resolves the user's storage config into a RawKVStore, or
|
|
180
|
+
// undefined when the provider is "local" or "memory" (handled by callers).
|
|
181
|
+
// ---------------------------------------------------------------------------
|
|
182
|
+
|
|
183
|
+
export const createRawKVStore = (config?: StateConfig): RawKVStore | undefined => {
|
|
184
|
+
const provider = config?.provider ?? "local";
|
|
185
|
+
|
|
186
|
+
if (provider === "upstash") {
|
|
187
|
+
const urlEnv = config?.urlEnv ?? (process.env.UPSTASH_REDIS_REST_URL ? "UPSTASH_REDIS_REST_URL" : "KV_REST_API_URL");
|
|
188
|
+
const tokenEnv = config?.tokenEnv ?? (process.env.UPSTASH_REDIS_REST_TOKEN ? "UPSTASH_REDIS_REST_TOKEN" : "KV_REST_API_TOKEN");
|
|
189
|
+
const url = process.env[urlEnv] ?? "";
|
|
190
|
+
const token = process.env[tokenEnv] ?? "";
|
|
191
|
+
if (url && token) return new UpstashKVStore(url, token);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (provider === "redis") {
|
|
195
|
+
const urlEnv = config?.urlEnv ?? "REDIS_URL";
|
|
196
|
+
const url = process.env[urlEnv] ?? "";
|
|
197
|
+
if (url) return new RedisKVStore(url);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (provider === "dynamodb") {
|
|
201
|
+
const table = config?.table ?? process.env.PONCHO_DYNAMODB_TABLE ?? "";
|
|
202
|
+
if (table) return new DynamoDbKVStore(table, config?.region);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return undefined;
|
|
206
|
+
};
|
package/src/memory.ts
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
slugifyStorageComponent,
|
|
9
9
|
STORAGE_SCHEMA_VERSION,
|
|
10
10
|
} from "./agent-identity.js";
|
|
11
|
+
import { createRawKVStore, type RawKVStore } from "./kv-store.js";
|
|
11
12
|
|
|
12
13
|
export interface MainMemory {
|
|
13
14
|
content: string;
|
|
@@ -168,25 +169,23 @@ class FileMainMemoryStore implements MemoryStore {
|
|
|
168
169
|
}
|
|
169
170
|
}
|
|
170
171
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
172
|
+
class KVBackedMemoryStore implements MemoryStore {
|
|
173
|
+
private readonly kv: RawKVStore;
|
|
174
|
+
private readonly storageKey: string;
|
|
175
|
+
private readonly ttl?: number;
|
|
176
|
+
private readonly memoryFallback: InMemoryMemoryStore;
|
|
174
177
|
|
|
175
|
-
constructor(ttl?: number) {
|
|
178
|
+
constructor(kv: RawKVStore, storageKey: string, ttl?: number) {
|
|
179
|
+
this.kv = kv;
|
|
180
|
+
this.storageKey = storageKey;
|
|
176
181
|
this.ttl = ttl;
|
|
177
182
|
this.memoryFallback = new InMemoryMemoryStore(ttl);
|
|
178
183
|
}
|
|
179
184
|
|
|
180
|
-
|
|
181
|
-
protected abstract setRaw(key: string, value: string): Promise<void>;
|
|
182
|
-
protected abstract setRawWithTtl(key: string, value: string, ttl: number): Promise<void>;
|
|
183
|
-
|
|
184
|
-
protected async readPayload(key: string): Promise<MainMemoryPayload> {
|
|
185
|
+
private async readPayload(): Promise<MainMemoryPayload> {
|
|
185
186
|
try {
|
|
186
|
-
const raw = await this.
|
|
187
|
-
if (!raw) {
|
|
188
|
-
return { main: { ...DEFAULT_MAIN_MEMORY } };
|
|
189
|
-
}
|
|
187
|
+
const raw = await this.kv.get(this.storageKey);
|
|
188
|
+
if (!raw) return { main: { ...DEFAULT_MAIN_MEMORY } };
|
|
190
189
|
const parsed = JSON.parse(raw) as MainMemoryPayload;
|
|
191
190
|
const content = typeof parsed.main?.content === "string" ? parsed.main.content : "";
|
|
192
191
|
const updatedAt = typeof parsed.main?.updatedAt === "number" ? parsed.main.updatedAt : 0;
|
|
@@ -197,259 +196,30 @@ abstract class KeyValueMainMemoryStoreBase implements MemoryStore {
|
|
|
197
196
|
}
|
|
198
197
|
}
|
|
199
198
|
|
|
200
|
-
|
|
199
|
+
private async writePayload(payload: MainMemoryPayload): Promise<void> {
|
|
201
200
|
try {
|
|
202
201
|
const serialized = JSON.stringify(payload);
|
|
203
202
|
if (typeof this.ttl === "number") {
|
|
204
|
-
await this.
|
|
203
|
+
await this.kv.setWithTtl(this.storageKey, serialized, Math.max(1, this.ttl));
|
|
205
204
|
} else {
|
|
206
|
-
await this.
|
|
205
|
+
await this.kv.set(this.storageKey, serialized);
|
|
207
206
|
}
|
|
208
207
|
} catch {
|
|
209
|
-
await this.memoryFallback.updateMainMemory({
|
|
210
|
-
content: payload.main.content,
|
|
211
|
-
});
|
|
208
|
+
await this.memoryFallback.updateMainMemory({ content: payload.main.content });
|
|
212
209
|
}
|
|
213
210
|
}
|
|
214
211
|
|
|
215
212
|
async getMainMemory(): Promise<MainMemory> {
|
|
216
|
-
const payload = await this.readPayload(
|
|
213
|
+
const payload = await this.readPayload();
|
|
217
214
|
return payload.main;
|
|
218
215
|
}
|
|
219
216
|
|
|
220
217
|
async updateMainMemory(input: { content: string }): Promise<MainMemory> {
|
|
221
|
-
const
|
|
222
|
-
|
|
223
|
-
payload
|
|
224
|
-
content: input.content.trim(),
|
|
225
|
-
updatedAt: Date.now(),
|
|
226
|
-
};
|
|
227
|
-
await this.writePayload(key, payload);
|
|
218
|
+
const payload = await this.readPayload();
|
|
219
|
+
payload.main = { content: input.content.trim(), updatedAt: Date.now() };
|
|
220
|
+
await this.writePayload(payload);
|
|
228
221
|
return payload.main;
|
|
229
222
|
}
|
|
230
|
-
|
|
231
|
-
protected abstract key(): string;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
class UpstashMemoryStore extends KeyValueMainMemoryStoreBase {
|
|
235
|
-
private readonly baseUrl: string;
|
|
236
|
-
private readonly token: string;
|
|
237
|
-
private readonly storageKey: string;
|
|
238
|
-
|
|
239
|
-
constructor(options: {
|
|
240
|
-
baseUrl: string;
|
|
241
|
-
token: string;
|
|
242
|
-
storageKey: string;
|
|
243
|
-
ttl?: number;
|
|
244
|
-
}) {
|
|
245
|
-
super(options.ttl);
|
|
246
|
-
this.baseUrl = options.baseUrl.replace(/\/+$/, "");
|
|
247
|
-
this.token = options.token;
|
|
248
|
-
this.storageKey = options.storageKey;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
protected key(): string {
|
|
252
|
-
return this.storageKey;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
private headers(): HeadersInit {
|
|
256
|
-
return {
|
|
257
|
-
Authorization: `Bearer ${this.token}`,
|
|
258
|
-
"Content-Type": "application/json",
|
|
259
|
-
};
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
protected async getRaw(key: string): Promise<string | undefined> {
|
|
263
|
-
const response = await fetch(`${this.baseUrl}/get/${encodeURIComponent(key)}`, {
|
|
264
|
-
method: "POST",
|
|
265
|
-
headers: this.headers(),
|
|
266
|
-
});
|
|
267
|
-
if (!response.ok) {
|
|
268
|
-
return undefined;
|
|
269
|
-
}
|
|
270
|
-
const payload = (await response.json()) as { result?: string | null };
|
|
271
|
-
return payload.result ?? undefined;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
protected async setRaw(key: string, value: string): Promise<void> {
|
|
275
|
-
await fetch(
|
|
276
|
-
`${this.baseUrl}/set/${encodeURIComponent(key)}/${encodeURIComponent(value)}`,
|
|
277
|
-
{ method: "POST", headers: this.headers() },
|
|
278
|
-
);
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
protected async setRawWithTtl(key: string, value: string, ttl: number): Promise<void> {
|
|
282
|
-
await fetch(
|
|
283
|
-
`${this.baseUrl}/setex/${encodeURIComponent(key)}/${Math.max(1, ttl)}/${encodeURIComponent(
|
|
284
|
-
value,
|
|
285
|
-
)}`,
|
|
286
|
-
{ method: "POST", headers: this.headers() },
|
|
287
|
-
);
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
class RedisMemoryStore extends KeyValueMainMemoryStoreBase {
|
|
292
|
-
private readonly storageKey: string;
|
|
293
|
-
private readonly clientPromise: Promise<
|
|
294
|
-
| {
|
|
295
|
-
get: (key: string) => Promise<string | null>;
|
|
296
|
-
set: (key: string, value: string, options?: { EX?: number }) => Promise<unknown>;
|
|
297
|
-
}
|
|
298
|
-
| undefined
|
|
299
|
-
>;
|
|
300
|
-
|
|
301
|
-
constructor(options: {
|
|
302
|
-
url: string;
|
|
303
|
-
storageKey: string;
|
|
304
|
-
ttl?: number;
|
|
305
|
-
}) {
|
|
306
|
-
super(options.ttl);
|
|
307
|
-
this.storageKey = options.storageKey;
|
|
308
|
-
this.clientPromise = (async () => {
|
|
309
|
-
try {
|
|
310
|
-
const redisModule = (await import("redis")) as unknown as {
|
|
311
|
-
createClient: (args: { url: string }) => {
|
|
312
|
-
connect: () => Promise<unknown>;
|
|
313
|
-
get: (key: string) => Promise<string | null>;
|
|
314
|
-
set: (key: string, value: string, options?: { EX?: number }) => Promise<unknown>;
|
|
315
|
-
};
|
|
316
|
-
};
|
|
317
|
-
const client = redisModule.createClient({ url: options.url });
|
|
318
|
-
await client.connect();
|
|
319
|
-
return client;
|
|
320
|
-
} catch {
|
|
321
|
-
return undefined;
|
|
322
|
-
}
|
|
323
|
-
})();
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
protected key(): string {
|
|
327
|
-
return this.storageKey;
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
protected async getRaw(key: string): Promise<string | undefined> {
|
|
331
|
-
const client = await this.clientPromise;
|
|
332
|
-
if (!client) {
|
|
333
|
-
throw new Error("Redis unavailable");
|
|
334
|
-
}
|
|
335
|
-
const value = await client.get(key);
|
|
336
|
-
return value ?? undefined;
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
protected async setRaw(key: string, value: string): Promise<void> {
|
|
340
|
-
const client = await this.clientPromise;
|
|
341
|
-
if (!client) {
|
|
342
|
-
throw new Error("Redis unavailable");
|
|
343
|
-
}
|
|
344
|
-
await client.set(key, value);
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
protected async setRawWithTtl(key: string, value: string, ttl: number): Promise<void> {
|
|
348
|
-
const client = await this.clientPromise;
|
|
349
|
-
if (!client) {
|
|
350
|
-
throw new Error("Redis unavailable");
|
|
351
|
-
}
|
|
352
|
-
await client.set(key, value, { EX: Math.max(1, ttl) });
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
class DynamoDbMemoryStore extends KeyValueMainMemoryStoreBase {
|
|
357
|
-
private readonly storageKey: string;
|
|
358
|
-
private readonly table: string;
|
|
359
|
-
private readonly clientPromise: Promise<
|
|
360
|
-
| {
|
|
361
|
-
send: (command: unknown) => Promise<unknown>;
|
|
362
|
-
GetItemCommand: new (input: unknown) => unknown;
|
|
363
|
-
PutItemCommand: new (input: unknown) => unknown;
|
|
364
|
-
}
|
|
365
|
-
| undefined
|
|
366
|
-
>;
|
|
367
|
-
|
|
368
|
-
constructor(options: {
|
|
369
|
-
table: string;
|
|
370
|
-
storageKey: string;
|
|
371
|
-
region?: string;
|
|
372
|
-
ttl?: number;
|
|
373
|
-
}) {
|
|
374
|
-
super(options.ttl);
|
|
375
|
-
this.storageKey = options.storageKey;
|
|
376
|
-
this.table = options.table;
|
|
377
|
-
this.clientPromise = (async () => {
|
|
378
|
-
try {
|
|
379
|
-
const module = (await import("@aws-sdk/client-dynamodb")) as {
|
|
380
|
-
DynamoDBClient: new (input: { region?: string }) => {
|
|
381
|
-
send: (command: unknown) => Promise<unknown>;
|
|
382
|
-
};
|
|
383
|
-
GetItemCommand: new (input: unknown) => unknown;
|
|
384
|
-
PutItemCommand: new (input: unknown) => unknown;
|
|
385
|
-
};
|
|
386
|
-
const client = new module.DynamoDBClient({ region: options.region });
|
|
387
|
-
return {
|
|
388
|
-
send: client.send.bind(client),
|
|
389
|
-
GetItemCommand: module.GetItemCommand,
|
|
390
|
-
PutItemCommand: module.PutItemCommand,
|
|
391
|
-
};
|
|
392
|
-
} catch {
|
|
393
|
-
return undefined;
|
|
394
|
-
}
|
|
395
|
-
})();
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
protected key(): string {
|
|
399
|
-
return this.storageKey;
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
protected async getRaw(key: string): Promise<string | undefined> {
|
|
403
|
-
const client = await this.clientPromise;
|
|
404
|
-
if (!client) {
|
|
405
|
-
throw new Error("DynamoDB unavailable");
|
|
406
|
-
}
|
|
407
|
-
const result = (await client.send(
|
|
408
|
-
new client.GetItemCommand({
|
|
409
|
-
TableName: this.table,
|
|
410
|
-
Key: { runId: { S: key } },
|
|
411
|
-
}),
|
|
412
|
-
)) as {
|
|
413
|
-
Item?: {
|
|
414
|
-
value?: { S?: string };
|
|
415
|
-
};
|
|
416
|
-
};
|
|
417
|
-
return result.Item?.value?.S;
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
protected async setRaw(key: string, value: string): Promise<void> {
|
|
421
|
-
const client = await this.clientPromise;
|
|
422
|
-
if (!client) {
|
|
423
|
-
throw new Error("DynamoDB unavailable");
|
|
424
|
-
}
|
|
425
|
-
await client.send(
|
|
426
|
-
new client.PutItemCommand({
|
|
427
|
-
TableName: this.table,
|
|
428
|
-
Item: {
|
|
429
|
-
runId: { S: key },
|
|
430
|
-
value: { S: value },
|
|
431
|
-
},
|
|
432
|
-
}),
|
|
433
|
-
);
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
protected async setRawWithTtl(key: string, value: string, ttl: number): Promise<void> {
|
|
437
|
-
const client = await this.clientPromise;
|
|
438
|
-
if (!client) {
|
|
439
|
-
throw new Error("DynamoDB unavailable");
|
|
440
|
-
}
|
|
441
|
-
const ttlEpoch = Math.floor(Date.now() / 1000) + Math.max(1, ttl);
|
|
442
|
-
await client.send(
|
|
443
|
-
new client.PutItemCommand({
|
|
444
|
-
TableName: this.table,
|
|
445
|
-
Item: {
|
|
446
|
-
runId: { S: key },
|
|
447
|
-
value: { S: value },
|
|
448
|
-
ttl: { N: String(ttlEpoch) },
|
|
449
|
-
},
|
|
450
|
-
}),
|
|
451
|
-
);
|
|
452
|
-
}
|
|
453
223
|
}
|
|
454
224
|
|
|
455
225
|
export const createMemoryStore = (
|
|
@@ -459,54 +229,19 @@ export const createMemoryStore = (
|
|
|
459
229
|
): MemoryStore => {
|
|
460
230
|
const provider = config?.provider ?? "local";
|
|
461
231
|
const ttl = config?.ttl;
|
|
462
|
-
const storageKey = `poncho:${STORAGE_SCHEMA_VERSION}:${slugifyStorageComponent(
|
|
463
|
-
agentId,
|
|
464
|
-
)}:memory:main`;
|
|
465
232
|
const workingDir = options?.workingDir ?? process.cwd();
|
|
233
|
+
|
|
466
234
|
if (provider === "local") {
|
|
467
235
|
return new FileMainMemoryStore(workingDir, ttl);
|
|
468
236
|
}
|
|
469
237
|
if (provider === "memory") {
|
|
470
238
|
return new InMemoryMemoryStore(ttl);
|
|
471
239
|
}
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
const
|
|
476
|
-
|
|
477
|
-
if (url && token) {
|
|
478
|
-
return new UpstashMemoryStore({
|
|
479
|
-
baseUrl: url,
|
|
480
|
-
token,
|
|
481
|
-
storageKey,
|
|
482
|
-
ttl,
|
|
483
|
-
});
|
|
484
|
-
}
|
|
485
|
-
return new InMemoryMemoryStore(ttl);
|
|
486
|
-
}
|
|
487
|
-
if (provider === "redis") {
|
|
488
|
-
const urlEnv = config?.urlEnv ?? "REDIS_URL";
|
|
489
|
-
const url = process.env[urlEnv] ?? "";
|
|
490
|
-
if (url) {
|
|
491
|
-
return new RedisMemoryStore({
|
|
492
|
-
url,
|
|
493
|
-
storageKey,
|
|
494
|
-
ttl,
|
|
495
|
-
});
|
|
496
|
-
}
|
|
497
|
-
return new InMemoryMemoryStore(ttl);
|
|
498
|
-
}
|
|
499
|
-
if (provider === "dynamodb") {
|
|
500
|
-
const table = config?.table ?? process.env.PONCHO_DYNAMODB_TABLE ?? "";
|
|
501
|
-
if (table) {
|
|
502
|
-
return new DynamoDbMemoryStore({
|
|
503
|
-
table,
|
|
504
|
-
storageKey,
|
|
505
|
-
region: config?.region,
|
|
506
|
-
ttl,
|
|
507
|
-
});
|
|
508
|
-
}
|
|
509
|
-
return new InMemoryMemoryStore(ttl);
|
|
240
|
+
|
|
241
|
+
const kv = createRawKVStore(config);
|
|
242
|
+
if (kv) {
|
|
243
|
+
const storageKey = `poncho:${STORAGE_SCHEMA_VERSION}:${slugifyStorageComponent(agentId)}:memory:main`;
|
|
244
|
+
return new KVBackedMemoryStore(kv, storageKey, ttl);
|
|
510
245
|
}
|
|
511
246
|
return new InMemoryMemoryStore(ttl);
|
|
512
247
|
};
|