Skip to content

Commit fb34e96

Browse files
author
Shaw
committed
more updates
1 parent 7691ba4 commit fb34e96

7 files changed

Lines changed: 121 additions & 64 deletions

File tree

cloud/packages/tests/unit/agent-hot-pool-cron.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,22 @@ describe("agent hot-pool cron schedule", () => {
1212
expect(CRON_FANOUT["* * * * *"]).toContain("/api/v1/cron/process-provisioning-jobs");
1313
});
1414

15+
test("warm pool replenisher runs every minute", () => {
16+
expect(CRON_FANOUT["* * * * *"]).toContain("/api/v1/cron/pool-replenish");
17+
});
18+
19+
test("warm pool drain-idle runs on the five-minute fanout", () => {
20+
expect(CRON_FANOUT["*/5 * * * *"]).toContain("/api/v1/cron/pool-drain-idle");
21+
});
22+
23+
test("warm pool health-check runs every two minutes", () => {
24+
expect(CRON_FANOUT["*/2 * * * *"]).toContain("/api/v1/cron/pool-health-check");
25+
});
26+
27+
test("warm pool image rollout runs every ten minutes", () => {
28+
expect(CRON_FANOUT["*/10 * * * *"]).toContain("/api/v1/cron/pool-image-rollout");
29+
});
30+
1531
test("accepts HETZNER_CLOUD_API_KEY as a Hetzner token alias", () => {
1632
const originalHcloud = process.env.HCLOUD_TOKEN;
1733
const originalHetznerToken = process.env.HETZNER_CLOUD_TOKEN;
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export function parseJsonObject<T extends Record<string, unknown>>(
2+
value: string,
3+
): T | null {
4+
try {
5+
const parsed: unknown = JSON.parse(value.trim());
6+
return parsed && typeof parsed === "object" && !Array.isArray(parsed)
7+
? (parsed as T)
8+
: null;
9+
} catch {
10+
return null;
11+
}
12+
}

plugins/plugin-shopify/src/actions/manage-customers.ts

Lines changed: 52 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@ import type {
88
Memory,
99
State,
1010
} from "@elizaos/core";
11-
import { logger, ModelType, parseToonKeyValue } from "@elizaos/core";
11+
import { logger, ModelType } from "@elizaos/core";
1212
import {
1313
SHOPIFY_SERVICE_TYPE,
1414
type ShopifyService,
1515
} from "../services/ShopifyService.js";
1616
import type { Customer } from "../types.js";
17+
import { getActionOptions } from "./confirmation.js";
18+
import { parseJsonObject } from "./json.js";
1719

1820
// ---------------------------------------------------------------------------
1921
// Helpers
@@ -40,36 +42,49 @@ type CustomerIntent =
4042
| { action: "list"; query: string | null }
4143
| { action: "search"; query: string };
4244

45+
function readNullableString(value: unknown): string | null {
46+
return typeof value === "string" &&
47+
value.trim().length > 0 &&
48+
value.trim().toLowerCase() !== "null"
49+
? value.trim()
50+
: null;
51+
}
52+
53+
function readCustomerIntent(options?: HandlerOptions): CustomerIntent | null {
54+
const params = getActionOptions(options);
55+
const candidate =
56+
params.intent && typeof params.intent === "object"
57+
? (params.intent as Record<string, unknown>)
58+
: params;
59+
const action = candidate.action;
60+
const query = readNullableString(candidate.query);
61+
if (action === "list") {
62+
return { action, query };
63+
}
64+
if (action === "search" && query) {
65+
return { action, query };
66+
}
67+
return null;
68+
}
69+
4370
async function classifyIntent(
4471
runtime: IAgentRuntime,
4572
text: string,
4673
): Promise<CustomerIntent | null> {
4774
const prompt = `Analyze the user message and determine what customer action they want.
48-
Respond with TOON only in one of these shapes:
49-
action: list
50-
query:
75+
Respond with JSON only in one of these shapes:
76+
{"action":"list","query":null}
5177
52-
action: search
53-
query: customer name, email, or other search term
78+
{"action":"search","query":"customer name, email, or other search term"}
5479
5580
User message: "${text}"
5681
`;
5782

5883
for (let i = 0; i < 2; i++) {
5984
const response = await runtime.useModel(ModelType.TEXT_SMALL, { prompt });
60-
const parsed = parseToonKeyValue<Record<string, unknown>>(response);
61-
const action = parsed?.action;
62-
const query =
63-
typeof parsed?.query === "string" &&
64-
parsed.query.trim().length > 0 &&
65-
parsed.query.trim().toLowerCase() !== "null"
66-
? parsed.query.trim()
67-
: null;
68-
if (action === "list") {
69-
return { action, query };
70-
}
71-
if (action === "search" && query) {
72-
return { action, query };
85+
const parsed = parseJsonObject<Record<string, unknown>>(response);
86+
if (parsed?.action) {
87+
return readCustomerIntent(parsed as HandlerOptions);
7388
}
7489
}
7590
return null;
@@ -123,7 +138,7 @@ export const manageCustomersAction: Action = {
123138
runtime: IAgentRuntime,
124139
message: Memory,
125140
_state?: State,
126-
_options?: HandlerOptions,
141+
options?: HandlerOptions,
127142
callback?: HandlerCallback,
128143
): Promise<ActionResult | undefined> => {
129144
const svc = runtime.getService<ShopifyService>(SHOPIFY_SERVICE_TYPE);
@@ -136,7 +151,8 @@ export const manageCustomersAction: Action = {
136151

137152
const text =
138153
typeof message.content?.text === "string" ? message.content.text : "";
139-
const intent = await classifyIntent(runtime, text);
154+
const intent =
155+
readCustomerIntent(options) ?? (await classifyIntent(runtime, text));
140156

141157
if (!intent) {
142158
await callback?.({
@@ -191,5 +207,20 @@ export const manageCustomersAction: Action = {
191207
}
192208
},
193209

210+
parameters: [
211+
{
212+
name: "action",
213+
description: "Customer action. One of: list, search.",
214+
required: false,
215+
schema: { type: "string" as const, enum: ["list", "search"] },
216+
},
217+
{
218+
name: "query",
219+
description: "Customer name, email, or other Shopify customer search term.",
220+
required: false,
221+
schema: { type: "string" as const },
222+
},
223+
],
224+
194225
examples,
195226
};

plugins/plugin-shopify/src/actions/manage-inventory.ts

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import type {
88
Memory,
99
State,
1010
} from "@elizaos/core";
11-
import { logger, ModelType, parseToonKeyValue } from "@elizaos/core";
11+
import { logger, ModelType } from "@elizaos/core";
1212
import {
1313
SHOPIFY_SERVICE_TYPE,
1414
type ShopifyService,
@@ -19,6 +19,7 @@ import {
1919
getActionOptions,
2020
isConfirmed,
2121
} from "./confirmation.js";
22+
import { parseJsonObject } from "./json.js";
2223

2324
// ---------------------------------------------------------------------------
2425
// Helpers
@@ -98,24 +99,21 @@ async function classifyIntent(
9899
text: string,
99100
): Promise<InventoryIntent | null> {
100101
const prompt = `Analyze the user message and determine what inventory action they want.
101-
Respond with TOON only in one of these shapes:
102-
action: check
103-
productQuery: product name or SKU to check
102+
Respond with JSON only in one of these shapes:
103+
{"action":"check","productQuery":"product name or SKU to check"}
104104
105-
action: adjust
106-
productQuery: product name
107-
delta: 5
108-
reason: reason
109-
(delta is positive to add stock, negative to remove stock)
105+
{"action":"adjust","productQuery":"product name","delta":5,"reason":"reason"}
110106
111-
action: locations
107+
{"action":"locations"}
108+
109+
For adjust, delta is positive to add stock and negative to remove stock.
112110
113111
User message: "${text}"
114112
`;
115113

116114
for (let i = 0; i < 2; i++) {
117115
const response = await runtime.useModel(ModelType.TEXT_SMALL, { prompt });
118-
const parsed = parseToonKeyValue<Record<string, unknown>>(response);
116+
const parsed = parseJsonObject<Record<string, unknown>>(response);
119117
if (parsed?.action) {
120118
return readInventoryIntent(parsed as HandlerOptions);
121119
}

plugins/plugin-shopify/src/actions/manage-orders.ts

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import type {
88
Memory,
99
State,
1010
} from "@elizaos/core";
11-
import { logger, ModelType, parseToonKeyValue } from "@elizaos/core";
11+
import { logger, ModelType } from "@elizaos/core";
1212
import {
1313
SHOPIFY_SERVICE_TYPE,
1414
type ShopifyService,
@@ -19,6 +19,7 @@ import {
1919
getActionOptions,
2020
isConfirmed,
2121
} from "./confirmation.js";
22+
import { parseJsonObject } from "./json.js";
2223

2324
// ---------------------------------------------------------------------------
2425
// Helpers
@@ -79,22 +80,19 @@ async function classifyIntent(
7980
text: string,
8081
): Promise<OrderIntent | null> {
8182
const prompt = `Analyze the user message and determine what order action they want.
82-
Respond with TOON only in one of these shapes:
83-
action: list
84-
query: optional filter like unfulfilled or last week
83+
Respond with JSON only in one of these shapes:
84+
{"action":"list","query":"optional filter like unfulfilled or last week"}
8585
86-
action: get
87-
orderName: order number like #1001 or 1001
86+
{"action":"get","orderName":"order number like #1001 or 1001"}
8887
89-
action: fulfill
90-
orderName: order number to fulfill
88+
{"action":"fulfill","orderName":"order number to fulfill"}
9189
9290
User message: "${text}"
9391
`;
9492

9593
for (let i = 0; i < 2; i++) {
9694
const response = await runtime.useModel(ModelType.TEXT_SMALL, { prompt });
97-
const parsed = parseToonKeyValue<Record<string, unknown>>(response);
95+
const parsed = parseJsonObject<Record<string, unknown>>(response);
9896
if (parsed?.action) {
9997
return readOrderIntent(parsed as HandlerOptions);
10098
}

plugins/plugin-shopify/src/actions/manage-products.ts

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import type {
88
Memory,
99
State,
1010
} from "@elizaos/core";
11-
import { logger, ModelType, parseToonKeyValue } from "@elizaos/core";
11+
import { logger, ModelType } from "@elizaos/core";
1212
import {
1313
SHOPIFY_SERVICE_TYPE,
1414
type ShopifyService,
@@ -19,6 +19,7 @@ import {
1919
getActionOptions,
2020
isConfirmed,
2121
} from "./confirmation.js";
22+
import { parseJsonObject } from "./json.js";
2223

2324
// ---------------------------------------------------------------------------
2425
// Helpers
@@ -111,29 +112,19 @@ async function classifyIntent(
111112
text: string,
112113
): Promise<ProductIntent | null> {
113114
const prompt = `Analyze the user message and determine what product action they want.
114-
Respond with TOON only in one of these shapes:
115-
action: list
116-
query: search term
115+
Respond with JSON only in one of these shapes:
116+
{"action":"list","query":"search term"}
117117
118-
action: create
119-
title: product title
120-
description: description
121-
productType: type
122-
vendor: vendor
123-
status: ACTIVE or DRAFT
118+
{"action":"create","title":"product title","description":"description","productType":"type","vendor":"vendor","status":"ACTIVE"}
124119
125-
action: update
126-
identifier: product title or handle to find
127-
title: new title
128-
description: new description
129-
status: ACTIVE or DRAFT or ARCHIVED
120+
{"action":"update","identifier":"product title or handle to find","title":"new title","description":"new description","status":"ACTIVE"}
130121
131122
User message: "${text}"
132123
`;
133124

134125
for (let i = 0; i < 2; i++) {
135126
const response = await runtime.useModel(ModelType.TEXT_SMALL, { prompt });
136-
const parsed = parseToonKeyValue<Record<string, unknown>>(response);
127+
const parsed = parseJsonObject<Record<string, unknown>>(response);
137128
if (parsed?.action) {
138129
return readProductIntent(parsed as HandlerOptions);
139130
}

plugins/plugin-shopify/src/actions/search-store.ts

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,13 @@ import type {
88
Memory,
99
State,
1010
} from "@elizaos/core";
11-
import { logger, ModelType, parseToonKeyValue } from "@elizaos/core";
11+
import { logger, ModelType } from "@elizaos/core";
1212
import {
1313
SHOPIFY_SERVICE_TYPE,
1414
type ShopifyService,
1515
} from "../services/ShopifyService.js";
1616
import type { Customer, Order, Product } from "../types.js";
17+
import { parseJsonObject } from "./json.js";
1718

1819
// ---------------------------------------------------------------------------
1920
// Helpers
@@ -86,9 +87,8 @@ async function classifyIntent(
8687
text: string,
8788
): Promise<SearchIntent | null> {
8889
const prompt = `Analyze the user message and determine what they want to search for in a Shopify store.
89-
Respond with TOON only:
90-
query: the search term
91-
scope: all | products | orders | customers
90+
Respond with JSON only:
91+
{"query":"the search term","scope":"all"}
9292
9393
Use "all" when the user does not specify a specific category, or mentions multiple.
9494
@@ -97,9 +97,20 @@ User message: "${text}"
9797

9898
for (let i = 0; i < 2; i++) {
9999
const response = await runtime.useModel(ModelType.TEXT_SMALL, { prompt });
100-
const parsed = parseToonKeyValue<Record<string, unknown>>(response);
101-
if (parsed?.query) {
102-
return parsed as unknown as SearchIntent;
100+
const parsed = parseJsonObject<Record<string, unknown>>(response);
101+
const query =
102+
typeof parsed?.query === "string" && parsed.query.trim().length > 0
103+
? parsed.query.trim()
104+
: null;
105+
const scope =
106+
parsed?.scope === "products" ||
107+
parsed?.scope === "orders" ||
108+
parsed?.scope === "customers" ||
109+
parsed?.scope === "all"
110+
? parsed.scope
111+
: "all";
112+
if (query) {
113+
return { query, scope };
103114
}
104115
}
105116
return null;

0 commit comments

Comments
 (0)