@jsm-mit/sultana-agent-tools-package 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.
- package/README.md +133 -0
- package/dist/create-salon-tools.d.ts +37 -0
- package/dist/create-salon-tools.d.ts.map +1 -0
- package/dist/create-salon-tools.js +30 -0
- package/dist/errors.d.ts +11 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +39 -0
- package/dist/ic-salon-core-port.d.ts +57 -0
- package/dist/ic-salon-core-port.d.ts.map +1 -0
- package/dist/ic-salon-core-port.js +160 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/persona.d.ts +18 -0
- package/dist/persona.d.ts.map +1 -0
- package/dist/persona.js +49 -0
- package/dist/salon-core-port.d.ts +144 -0
- package/dist/salon-core-port.d.ts.map +1 -0
- package/dist/salon-core-port.js +11 -0
- package/dist/tools/args.d.ts +30 -0
- package/dist/tools/args.d.ts.map +1 -0
- package/dist/tools/args.js +89 -0
- package/dist/tools/promos.d.ts +7 -0
- package/dist/tools/promos.d.ts.map +1 -0
- package/dist/tools/promos.js +438 -0
- package/dist/tools/schedule.d.ts +4 -0
- package/dist/tools/schedule.d.ts.map +1 -0
- package/dist/tools/schedule.js +300 -0
- package/dist/tools/services.d.ts +7 -0
- package/dist/tools/services.d.ts.map +1 -0
- package/dist/tools/services.js +309 -0
- package/dist/types.d.ts +63 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +14 -0
- package/package.json +48 -0
package/README.md
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# sultana-agent-tools-package
|
|
2
|
+
|
|
3
|
+
`@jsm-mit/sultana-agent-tools-package` — the tool layer that lets an LLM agent edit a Sultana
|
|
4
|
+
salon by conversation: services, promos and working hours.
|
|
5
|
+
|
|
6
|
+
It is **framework-agnostic**. A tool is `{ name, description, parameters, progress, execute }`, which is what
|
|
7
|
+
OpenAI, Anthropic and `@jsm-mit/chat-agent-package` all want from a function tool, so wiring this
|
|
8
|
+
into an agent host is registration rather than translation. The package depends on the published
|
|
9
|
+
canister wrapper and nothing else — no model provider, no agent framework.
|
|
10
|
+
|
|
11
|
+
## Use
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { createSalonTools, SALON_AGENT_PERSONA_PL } from "@jsm-mit/sultana-agent-tools-package";
|
|
15
|
+
|
|
16
|
+
const tools = createSalonTools({
|
|
17
|
+
canisterId: process.env.SULTANA_CORE_CANISTER_ID!,
|
|
18
|
+
identity, // the salon owner's identity (or, later, a granted agent principal)
|
|
19
|
+
salonId,
|
|
20
|
+
});
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
`salonId` is captured at construction and is **never a tool argument** — a model holding these
|
|
24
|
+
tools cannot reach another salon's data whatever it sends.
|
|
25
|
+
|
|
26
|
+
### Read-only set
|
|
27
|
+
|
|
28
|
+
An assistant that runs without the owner's identity takes the read-only set and its own persona:
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import { createSalonReadTools, SALON_READ_PERSONA_PL } from "@jsm-mit/sultana-agent-tools-package";
|
|
32
|
+
|
|
33
|
+
const tools = createSalonReadTools({
|
|
34
|
+
canisterId: process.env.SULTANA_CORE_CANISTER_ID!,
|
|
35
|
+
salonId, // no identity: every call is a public query, made anonymously
|
|
36
|
+
});
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
It holds `list_services`, `list_promos` and `find_service_type` — nothing that writes. The port
|
|
40
|
+
behind it is built with `access: "public"`, so `list_services` calls the visitor's query: a
|
|
41
|
+
visitor, and so this agent, sees **ACTIVE services and ACTIVE promos only** (and no worker who has
|
|
42
|
+
not claimed their account). The two list tools say so in their description and summary, and
|
|
43
|
+
`SALON_READ_PERSONA_PL` tells the model to mention it and to send edits to the "Usługi" and
|
|
44
|
+
"Promocje" panel pages. No identity is needed; if a host passes the owner's anyway, the port still
|
|
45
|
+
drops inactive promos, so the tool keeps its promise.
|
|
46
|
+
|
|
47
|
+
`createSalonReadToolsFromPort(port)` is the same set over any port, for tests.
|
|
48
|
+
|
|
49
|
+
## Tools
|
|
50
|
+
|
|
51
|
+
Every tool carries `progress` — a short Polish line in the present continuous, ending with "…",
|
|
52
|
+
that a chat shows while the tool runs (`@jsm-mit/chat-agent-package` reads `Tool.progress`). It is
|
|
53
|
+
never sent to the model. A write tool previewing with `confirmed: false` shows the same line.
|
|
54
|
+
|
|
55
|
+
| tool | writes | progress |
|
|
56
|
+
|---|---|---|
|
|
57
|
+
| `list_services` | | Sprawdzam listę usług… |
|
|
58
|
+
| `find_service_type` | | Szukam typu usługi… |
|
|
59
|
+
| `list_workers` | | Sprawdzam zespół… |
|
|
60
|
+
| `add_service` | yes | Dodaję usługę… |
|
|
61
|
+
| `update_service` | yes | Zmieniam usługę… |
|
|
62
|
+
| `remove_service` | yes | Usuwam usługę… |
|
|
63
|
+
| `get_weekly_hours` | | Sprawdzam grafik tygodniowy… |
|
|
64
|
+
| `set_weekly_hours` | yes | Ustawiam grafik… |
|
|
65
|
+
| `get_day_schedule` | | Sprawdzam grafik na ten dzień… |
|
|
66
|
+
| `set_day_off` | yes | Ustawiam dzień wolny… |
|
|
67
|
+
| `set_busy_hours` | yes | Zapisuję godziny zajęte… |
|
|
68
|
+
| `clear_daily_override` | yes | Przywracam grafik tygodniowy… |
|
|
69
|
+
| `list_media` | | Sprawdzam bibliotekę materiałów… |
|
|
70
|
+
| `list_promos` | | Sprawdzam promocje… |
|
|
71
|
+
| `add_promo` | yes | Zakładam promocję… |
|
|
72
|
+
| `update_promo` | yes | Zmieniam promocję… |
|
|
73
|
+
| `set_promo_active` | yes | Zmieniam status promocji… |
|
|
74
|
+
| `remove_promo` | yes | Kasuję promocję… |
|
|
75
|
+
| `list_discount_codes` | | Sprawdzam kody rabatowe… |
|
|
76
|
+
| `add_discount_code` | yes | Dodaję kod rabatowy… |
|
|
77
|
+
| `remove_discount_code` | yes | Usuwam kod rabatowy… |
|
|
78
|
+
|
|
79
|
+
`createSalonTools` returns all 21; `createSalonReadTools` returns `list_services`, `list_promos`
|
|
80
|
+
and `find_service_type`.
|
|
81
|
+
|
|
82
|
+
## What the tools guarantee
|
|
83
|
+
|
|
84
|
+
- **`execute` never throws.** Every call returns `{status: "ok" | "confirmation_required" | "error"}`
|
|
85
|
+
so an agent can read a failure and talk about it.
|
|
86
|
+
- **Confirmation is a schema field, not a prompt rule.** Called with `confirmed: false`, a write
|
|
87
|
+
tool makes no canister call and returns a sentence built from *resolved* arguments — worker names
|
|
88
|
+
and service-type labels already looked up — plus the exact arguments to send back. A persona can
|
|
89
|
+
be talked out of a rule; a required field cannot.
|
|
90
|
+
- **Irreversible operations need the entity's own name** typed back in `confirmationPhrase`.
|
|
91
|
+
- **A partial edit is merged.** `updateSalonService` replaces the whole record on the canister, so
|
|
92
|
+
"just change the price" would otherwise unassign the team.
|
|
93
|
+
- **Units are fixed at the boundary**: price is whole zloty, duration is minutes rounded up to a
|
|
94
|
+
multiple of 5 (the availability grid), and the tool says when it rounded.
|
|
95
|
+
- **Service-type ids come from the catalogue.** `find_service_type` uses the same matcher as both
|
|
96
|
+
frontends, and a write with an unknown id is refused before the canister is called.
|
|
97
|
+
|
|
98
|
+
## Scripts
|
|
99
|
+
|
|
100
|
+
| script | what it does |
|
|
101
|
+
|---|---|
|
|
102
|
+
| `npm run build` | `tsc` for `dist/`, then a type-check over tests and sandbox |
|
|
103
|
+
| `npm test` | unit tests against an in-memory salon — no replica, no network |
|
|
104
|
+
| `npm run whoami` | setup check: the identity, its salons, the service-type catalogue |
|
|
105
|
+
| `npm run sandbox -- --fake` | talk to the tools against an in-memory salon |
|
|
106
|
+
| `npm run sandbox` | the same conversation against a real canister |
|
|
107
|
+
| `npm run test-tools` | the round trip against a live canister (creates its own salon) |
|
|
108
|
+
| `npm run publish-public` | publishes to npm from master — see below |
|
|
109
|
+
|
|
110
|
+
## Testing by talking
|
|
111
|
+
|
|
112
|
+
`npm run sandbox -- --fake` is the loop to iterate in: a real model, real tool calls, an in-memory
|
|
113
|
+
salon. Every call is printed with its arguments and its result, so a tool description that misleads
|
|
114
|
+
the model shows up immediately — and a wrong answer costs no cycles and no daily action quota.
|
|
115
|
+
|
|
116
|
+
Drop `--fake` to run the same conversation against a canister with the owner's identity
|
|
117
|
+
(`OWNER1_PEM`). Start with `npm run whoami`: it prints the principal the PEM makes you, the salons
|
|
118
|
+
that identity owns on that canister — one of which is your `SULTANA_SALON_ID` — and whether the
|
|
119
|
+
service-type catalogue is seeded at all. `npm run test-tools` is the deterministic version of that: every write tool against
|
|
120
|
+
a live canister, each verified by reading it back. Both talk to a canister, so **a human starts
|
|
121
|
+
them, not Claude**.
|
|
122
|
+
|
|
123
|
+
## Publishing
|
|
124
|
+
|
|
125
|
+
Bump `version` in `package.json` by hand, then `npm run publish-public` from `master`. The script
|
|
126
|
+
refuses a `master` that differs from `origin/master`, any uncommitted change besides the version
|
|
127
|
+
bump, and a version already on the registry. It builds from a clean `dist/`, runs the unit
|
|
128
|
+
tests, publishes with `--access public`, and only then commits "Version bump to <version>" and
|
|
129
|
+
pushes `master` — so a failed publish leaves nothing claiming a release. A version whose bump is
|
|
130
|
+
already committed publishes without a new commit.
|
|
131
|
+
|
|
132
|
+
There is no canister-commit stamp: this package wraps no canister of its own. The tarball holds
|
|
133
|
+
`dist/`, `README.md` and `package.json` — no tests, no sandbox, no `.env`.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { Identity } from "@icp-sdk/core/agent";
|
|
2
|
+
import { type IcSalonCorePortOptions } from "./ic-salon-core-port.js";
|
|
3
|
+
import type { SalonCorePort } from "./salon-core-port.js";
|
|
4
|
+
import type { AgentTool } from "./types.js";
|
|
5
|
+
export interface CreateSalonToolsOptions extends Omit<IcSalonCorePortOptions, "language" | "access"> {
|
|
6
|
+
/** Language of service-type labels. Defaults to Polish. */
|
|
7
|
+
language?: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Builds the tool set for ONE salon. `salonId` is captured here and never appears as a tool
|
|
11
|
+
* argument, so a model holding these tools cannot reach another salon's data whatever it sends.
|
|
12
|
+
*/
|
|
13
|
+
export declare function createSalonTools(options: CreateSalonToolsOptions): AgentTool[];
|
|
14
|
+
/** The same tool set over any port — the seam tests and the sandbox use. */
|
|
15
|
+
export declare function createSalonToolsFromPort(port: SalonCorePort): AgentTool[];
|
|
16
|
+
export interface CreateSalonReadToolsOptions {
|
|
17
|
+
canisterId: string;
|
|
18
|
+
salonId: string;
|
|
19
|
+
/**
|
|
20
|
+
* Omit it — the read set is built for a host that holds no owner identity, and every call it
|
|
21
|
+
* makes is a public query. With the owner's identity the canister would show the whole promo
|
|
22
|
+
* library; the port drops inactive promos in public access, so the tools still keep their word.
|
|
23
|
+
*/
|
|
24
|
+
identity?: Identity;
|
|
25
|
+
/** Language of service-type labels. Defaults to Polish. */
|
|
26
|
+
language?: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The read-only tool set for ONE salon: `list_services`, `list_promos` and `find_service_type`,
|
|
30
|
+
* nothing that writes. Reads what a visitor sees — ACTIVE services and ACTIVE promos only — so an
|
|
31
|
+
* assistant can run without the owner's identity.
|
|
32
|
+
*/
|
|
33
|
+
export declare function createSalonReadTools(options: CreateSalonReadToolsOptions): AgentTool[];
|
|
34
|
+
/** The read-only set over any port. The port must return the public view — the tool texts tell the
|
|
35
|
+
* model that inactive services and promos are not in the lists. */
|
|
36
|
+
export declare function createSalonReadToolsFromPort(port: SalonCorePort): AgentTool[];
|
|
37
|
+
//# sourceMappingURL=create-salon-tools.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-salon-tools.d.ts","sourceRoot":"","sources":["../src/create-salon-tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAmB,KAAK,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AACvF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAI1D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C,MAAM,WAAW,uBAAwB,SAAQ,IAAI,CAAC,sBAAsB,EAAE,UAAU,GAAG,QAAQ,CAAC;IAChG,2DAA2D;IAC3D,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,uBAAuB,GAAG,SAAS,EAAE,CAG9E;AAED,4EAA4E;AAC5E,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,aAAa,GAAG,SAAS,EAAE,CAEzE;AAED,MAAM,WAAW,2BAA2B;IACxC,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,2DAA2D;IAC3D,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,2BAA2B,GAAG,SAAS,EAAE,CAGtF;AAED;mEACmE;AACnE,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,aAAa,GAAG,SAAS,EAAE,CAE7E"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { IcSalonCorePort } from "./ic-salon-core-port.js";
|
|
2
|
+
import { createPromoTools, listPromosTool } from "./tools/promos.js";
|
|
3
|
+
import { createScheduleTools } from "./tools/schedule.js";
|
|
4
|
+
import { createServiceTools, findServiceTypeTool, listServicesTool } from "./tools/services.js";
|
|
5
|
+
/**
|
|
6
|
+
* Builds the tool set for ONE salon. `salonId` is captured here and never appears as a tool
|
|
7
|
+
* argument, so a model holding these tools cannot reach another salon's data whatever it sends.
|
|
8
|
+
*/
|
|
9
|
+
export function createSalonTools(options) {
|
|
10
|
+
const port = new IcSalonCorePort({ ...options, language: options.language ?? "pl", access: "owner" });
|
|
11
|
+
return createSalonToolsFromPort(port);
|
|
12
|
+
}
|
|
13
|
+
/** The same tool set over any port — the seam tests and the sandbox use. */
|
|
14
|
+
export function createSalonToolsFromPort(port) {
|
|
15
|
+
return [...createServiceTools(port), ...createScheduleTools(port), ...createPromoTools(port)];
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The read-only tool set for ONE salon: `list_services`, `list_promos` and `find_service_type`,
|
|
19
|
+
* nothing that writes. Reads what a visitor sees — ACTIVE services and ACTIVE promos only — so an
|
|
20
|
+
* assistant can run without the owner's identity.
|
|
21
|
+
*/
|
|
22
|
+
export function createSalonReadTools(options) {
|
|
23
|
+
const port = new IcSalonCorePort({ ...options, language: options.language ?? "pl", access: "public" });
|
|
24
|
+
return createSalonReadToolsFromPort(port);
|
|
25
|
+
}
|
|
26
|
+
/** The read-only set over any port. The port must return the public view — the tool texts tell the
|
|
27
|
+
* model that inactive services and promos are not in the lists. */
|
|
28
|
+
export function createSalonReadToolsFromPort(port) {
|
|
29
|
+
return [listServicesTool(port, "public"), listPromosTool(port, "public"), findServiceTypeTool(port)];
|
|
30
|
+
}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type ToolResult } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Maps what the canister wrapper throws onto a tool result. The wrapper has exactly three failure
|
|
4
|
+
* shapes (see `ActorBase.executeFunctionAsyncUnsafe`): a decoded business error, a rejection by
|
|
5
|
+
* `system func inspect`, and everything else.
|
|
6
|
+
*
|
|
7
|
+
* `fallbackSummary` is what the agent reads when the canister gives us nothing better — write it
|
|
8
|
+
* in the language the agent speaks to the owner.
|
|
9
|
+
*/
|
|
10
|
+
export declare function toToolError(error: unknown, fallbackSummary: string): ToolResult;
|
|
11
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAA2B,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAQtE;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,GAAG,UAAU,CAsB/E"}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { err } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Maps what the canister wrapper throws onto a tool result. The wrapper has exactly three failure
|
|
4
|
+
* shapes (see `ActorBase.executeFunctionAsyncUnsafe`): a decoded business error, a rejection by
|
|
5
|
+
* `system func inspect`, and everything else.
|
|
6
|
+
*
|
|
7
|
+
* `fallbackSummary` is what the agent reads when the canister gives us nothing better — write it
|
|
8
|
+
* in the language the agent speaks to the owner.
|
|
9
|
+
*/
|
|
10
|
+
export function toToolError(error, fallbackSummary) {
|
|
11
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
12
|
+
const cause = (error instanceof Error ? error.cause : undefined);
|
|
13
|
+
if (message === "CallRefusedAtInspectionStage") {
|
|
14
|
+
return err("quota_exceeded", "Kanister odrzucił wywołanie przed wykonaniem — najczęściej wyczerpany dzienny limit akcji albo zbyt duży ładunek.");
|
|
15
|
+
}
|
|
16
|
+
if (message === "CriticalCanisterError") {
|
|
17
|
+
return err("unavailable", "Brak łączności z kanistrem albo kanister przerwał wykonanie. Spróbuj ponownie za chwilę.");
|
|
18
|
+
}
|
|
19
|
+
if (message === "CanisterError") {
|
|
20
|
+
const detail = cause?.errorMessage?.trim();
|
|
21
|
+
const code = mapErrorKey(cause?.errorKey);
|
|
22
|
+
return err(code, detail ? `${fallbackSummary} (${detail})` : fallbackSummary);
|
|
23
|
+
}
|
|
24
|
+
return err("canister_error", fallbackSummary);
|
|
25
|
+
}
|
|
26
|
+
function mapErrorKey(errorKey) {
|
|
27
|
+
switch (errorKey) {
|
|
28
|
+
case "NotAuthorized":
|
|
29
|
+
return "not_authorized";
|
|
30
|
+
case "NotFound":
|
|
31
|
+
case "ReturnsNull":
|
|
32
|
+
return "not_found";
|
|
33
|
+
case "InvalidData":
|
|
34
|
+
case "AlreadyExists":
|
|
35
|
+
return "invalid_arguments";
|
|
36
|
+
default:
|
|
37
|
+
return "canister_error";
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { Identity } from "@icp-sdk/core/agent";
|
|
2
|
+
import type { DiscountCodeInput, DiscountCodeView, MediaView, PromoView, PromoWriteInput, SalonAccess, SalonCorePort, TimeRange, ServiceTypeView, ServiceView, ServiceWriteInput, WorkerView } from "./salon-core-port.js";
|
|
3
|
+
export interface IcSalonCorePortOptions {
|
|
4
|
+
canisterId: string;
|
|
5
|
+
/** Omit for an anonymous identity — every write will then be refused, which is only useful in
|
|
6
|
+
* a read-only demo. */
|
|
7
|
+
identity?: Identity;
|
|
8
|
+
salonId: string;
|
|
9
|
+
/** Language of service-type labels the agent sees and quotes back to the owner. */
|
|
10
|
+
language: string;
|
|
11
|
+
/**
|
|
12
|
+
* Which services endpoint `listServices` reads. `"owner"` (the default) is the owner-only
|
|
13
|
+
* query — the whole catalogue. `"public"` is the visitor's query: no identity needed, ACTIVE
|
|
14
|
+
* services only, and without workers who have not claimed their account yet. Every other
|
|
15
|
+
* method is the same in both.
|
|
16
|
+
*/
|
|
17
|
+
access?: SalonAccess;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* The port over the published canister wrapper. Conversions live here and nowhere else: the
|
|
21
|
+
* canister speaks `bigint` and `Principal`, the tools speak numbers and strings.
|
|
22
|
+
*/
|
|
23
|
+
export declare class IcSalonCorePort implements SalonCorePort {
|
|
24
|
+
private readonly salonId;
|
|
25
|
+
private readonly language;
|
|
26
|
+
private readonly access;
|
|
27
|
+
private readonly services;
|
|
28
|
+
private readonly hr;
|
|
29
|
+
private readonly serviceTypes;
|
|
30
|
+
private readonly availability;
|
|
31
|
+
private readonly assets;
|
|
32
|
+
private readonly promo;
|
|
33
|
+
private readonly discountCodes;
|
|
34
|
+
constructor(options: IcSalonCorePortOptions);
|
|
35
|
+
listServices(): Promise<ServiceView[]>;
|
|
36
|
+
addService(input: ServiceWriteInput): Promise<string>;
|
|
37
|
+
updateService(serviceId: string, input: ServiceWriteInput): Promise<void>;
|
|
38
|
+
removeService(serviceId: string): Promise<void>;
|
|
39
|
+
listWorkers(): Promise<WorkerView[]>;
|
|
40
|
+
findServiceTypes(query: string): Promise<ServiceTypeView[]>;
|
|
41
|
+
getWeeklyHours(workerId: string, day: number): Promise<TimeRange[]>;
|
|
42
|
+
setWeeklyHours(workerId: string, day: number, ranges: TimeRange[]): Promise<void>;
|
|
43
|
+
getDailyBusy(workerId: string, date: string): Promise<TimeRange[]>;
|
|
44
|
+
setDailyBusy(workerId: string, date: string, ranges: TimeRange[]): Promise<void>;
|
|
45
|
+
setDayOff(workerId: string, date: string): Promise<void>;
|
|
46
|
+
clearDailyOverride(workerId: string, date: string): Promise<void>;
|
|
47
|
+
listMedia(): Promise<MediaView[]>;
|
|
48
|
+
listPromos(): Promise<PromoView[]>;
|
|
49
|
+
addPromo(input: PromoWriteInput): Promise<string>;
|
|
50
|
+
updatePromo(promoId: string, input: PromoWriteInput): Promise<void>;
|
|
51
|
+
setPromoActive(promoId: string, active: boolean): Promise<void>;
|
|
52
|
+
removePromo(promoId: string): Promise<void>;
|
|
53
|
+
listDiscountCodes(): Promise<DiscountCodeView[]>;
|
|
54
|
+
addDiscountCode(input: DiscountCodeInput): Promise<void>;
|
|
55
|
+
removeDiscountCode(code: string): Promise<void>;
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=ic-salon-core-port.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ic-salon-core-port.d.ts","sourceRoot":"","sources":["../src/ic-salon-core-port.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { Principal } from "@icp-sdk/core/principal";
|
|
2
|
+
import { AssetsActor, AvailabilityActor, DiscountCodesActor, filterServiceTypes, HrActor, PromoActor, promoMusicOf, SalonServicesActor, ServiceTypesActor, } from "@jsm-mit/sultana-core-motoko-package";
|
|
3
|
+
/**
|
|
4
|
+
* The port over the published canister wrapper. Conversions live here and nowhere else: the
|
|
5
|
+
* canister speaks `bigint` and `Principal`, the tools speak numbers and strings.
|
|
6
|
+
*/
|
|
7
|
+
export class IcSalonCorePort {
|
|
8
|
+
salonId;
|
|
9
|
+
language;
|
|
10
|
+
access;
|
|
11
|
+
services;
|
|
12
|
+
hr;
|
|
13
|
+
serviceTypes;
|
|
14
|
+
availability;
|
|
15
|
+
assets;
|
|
16
|
+
promo;
|
|
17
|
+
discountCodes;
|
|
18
|
+
constructor(options) {
|
|
19
|
+
this.salonId = options.salonId;
|
|
20
|
+
this.language = options.language;
|
|
21
|
+
this.access = options.access ?? "owner";
|
|
22
|
+
this.services = new SalonServicesActor(options.canisterId, options.identity);
|
|
23
|
+
this.hr = new HrActor(options.canisterId, options.identity);
|
|
24
|
+
this.serviceTypes = new ServiceTypesActor(options.canisterId, options.identity);
|
|
25
|
+
this.availability = new AvailabilityActor(options.canisterId, options.identity);
|
|
26
|
+
this.assets = new AssetsActor(options.canisterId, options.identity);
|
|
27
|
+
this.promo = new PromoActor(options.canisterId, options.identity);
|
|
28
|
+
this.discountCodes = new DiscountCodesActor(options.canisterId, options.identity);
|
|
29
|
+
}
|
|
30
|
+
async listServices() {
|
|
31
|
+
// Both queries return the same candid record, so one mapping serves both.
|
|
32
|
+
const services = this.access === "public"
|
|
33
|
+
? await this.services.getSalonServicesPublicAsyncUnsafe(this.salonId)
|
|
34
|
+
: await this.services.getSalonServicesBySalonIdAsyncUnsafe(this.salonId);
|
|
35
|
+
return services.map((service) => ({
|
|
36
|
+
id: service.id,
|
|
37
|
+
name: service.name,
|
|
38
|
+
pricePln: Number(service.price),
|
|
39
|
+
durationMinutes: Number(service.duration),
|
|
40
|
+
active: service.active,
|
|
41
|
+
serviceTypeIds: [...service.serviceTypeIds],
|
|
42
|
+
workerIds: service.workerIds.map((worker) => worker.toText()),
|
|
43
|
+
}));
|
|
44
|
+
}
|
|
45
|
+
async addService(input) {
|
|
46
|
+
return this.services.addSalonServiceAsyncUnsafe(input.active, BigInt(input.durationMinutes), input.name, BigInt(input.pricePln), this.salonId, input.serviceTypeIds, input.workerIds);
|
|
47
|
+
}
|
|
48
|
+
async updateService(serviceId, input) {
|
|
49
|
+
await this.services.updateSalonServiceAsyncUnsafe(input.active, BigInt(input.durationMinutes), input.name, BigInt(input.pricePln), serviceId, input.serviceTypeIds, input.workerIds);
|
|
50
|
+
}
|
|
51
|
+
async removeService(serviceId) {
|
|
52
|
+
await this.services.removeSalonServiceAsyncUnsafe(serviceId);
|
|
53
|
+
}
|
|
54
|
+
async listWorkers() {
|
|
55
|
+
const workers = await this.hr.getWorkersBySalonIdAsyncUnsafe(this.salonId);
|
|
56
|
+
return workers.map((worker) => ({
|
|
57
|
+
id: worker.id.toText(),
|
|
58
|
+
name: worker.name,
|
|
59
|
+
// A pending code means the placeholder principal is still waiting to be claimed. The
|
|
60
|
+
// owner can already assign and schedule against it, so it is a usable worker either way.
|
|
61
|
+
linked: worker.pendingCode === undefined,
|
|
62
|
+
}));
|
|
63
|
+
}
|
|
64
|
+
async findServiceTypes(query) {
|
|
65
|
+
const catalog = await this.serviceTypes.getServiceTypesCatalogAsyncUnsafe(this.language);
|
|
66
|
+
return filterServiceTypes(catalog, query).map((entry) => ({
|
|
67
|
+
id: entry.id,
|
|
68
|
+
label: entry.text,
|
|
69
|
+
category: entry.category,
|
|
70
|
+
}));
|
|
71
|
+
}
|
|
72
|
+
async getWeeklyHours(workerId, day) {
|
|
73
|
+
return this.availability.getDayFromWeeklyTemplateAsyncUnsafe(BigInt(day), this.salonId, Principal.fromText(workerId));
|
|
74
|
+
}
|
|
75
|
+
async setWeeklyHours(workerId, day, ranges) {
|
|
76
|
+
await this.availability.setWeeklyTemplateAsyncUnsafe(BigInt(day), ranges, this.salonId, Principal.fromText(workerId));
|
|
77
|
+
}
|
|
78
|
+
async getDailyBusy(workerId, date) {
|
|
79
|
+
return this.availability.getDailyScheduleAsyncUnsafe(date, Principal.fromText(workerId));
|
|
80
|
+
}
|
|
81
|
+
async setDailyBusy(workerId, date, ranges) {
|
|
82
|
+
await this.availability.setDailyScheduleAsyncUnsafe(date, ranges, Principal.fromText(workerId));
|
|
83
|
+
}
|
|
84
|
+
async setDayOff(workerId, date) {
|
|
85
|
+
await this.availability.setADayOffAsyncUnsafe(date, Principal.fromText(workerId));
|
|
86
|
+
}
|
|
87
|
+
async clearDailyOverride(workerId, date) {
|
|
88
|
+
await this.availability.removeDailyScheduleAsyncUnsafe(date, Principal.fromText(workerId));
|
|
89
|
+
}
|
|
90
|
+
async listMedia() {
|
|
91
|
+
const assets = await this.assets.getSalonAssetsAsyncUnsafe(this.salonId);
|
|
92
|
+
return assets
|
|
93
|
+
// Only a #Ready entry can carry a promo; a #Pending one is still transcoding and a
|
|
94
|
+
// #Failed one would be refused by the canister.
|
|
95
|
+
.filter((asset) => "Ready" in asset.status)
|
|
96
|
+
.map((asset) => ({
|
|
97
|
+
assetId: asset.assetId,
|
|
98
|
+
name: asset.name,
|
|
99
|
+
kind: "Image" in asset.kind ? "image" : "Video" in asset.kind ? "video" : "audio",
|
|
100
|
+
}));
|
|
101
|
+
}
|
|
102
|
+
async listPromos() {
|
|
103
|
+
const promos = await this.promo.getSalonPromosAsyncUnsafe(this.salonId);
|
|
104
|
+
// The canister gives the owner the whole library; a public tool set promises active
|
|
105
|
+
// promos only, whatever identity its host passed in.
|
|
106
|
+
const visible = this.access === "public" ? promos.filter((promo) => promo.active) : promos;
|
|
107
|
+
return visible.map((promo) => ({
|
|
108
|
+
id: promo.id,
|
|
109
|
+
name: promo.name,
|
|
110
|
+
active: promo.active,
|
|
111
|
+
assetId: promo.assetId,
|
|
112
|
+
textLines: [...promo.textLines],
|
|
113
|
+
chips: [...promo.chips],
|
|
114
|
+
buttonLabel: promo.cta.buttonLabel[0],
|
|
115
|
+
discountCode: promo.cta.discountCode[0],
|
|
116
|
+
targetServiceId: "SalonService" in promo.target ? promo.target.SalonService : undefined,
|
|
117
|
+
hasMusic: promo.music.length > 0,
|
|
118
|
+
music: promoMusicOf(promo) ?? undefined,
|
|
119
|
+
}));
|
|
120
|
+
}
|
|
121
|
+
async addPromo(input) {
|
|
122
|
+
return this.promo.addPromoAsyncUnsafe(this.salonId, input.name, input.assetId, toContent(input), input.targetServiceId);
|
|
123
|
+
}
|
|
124
|
+
async updatePromo(promoId, input) {
|
|
125
|
+
await this.promo.updatePromoAsyncUnsafe(this.salonId, promoId, input.name, input.assetId, toContent(input), input.targetServiceId);
|
|
126
|
+
}
|
|
127
|
+
async setPromoActive(promoId, active) {
|
|
128
|
+
await this.promo.setPromoActiveAsyncUnsafe(this.salonId, promoId, active);
|
|
129
|
+
}
|
|
130
|
+
async removePromo(promoId) {
|
|
131
|
+
await this.promo.removePromoAsyncUnsafe(this.salonId, promoId);
|
|
132
|
+
}
|
|
133
|
+
async listDiscountCodes() {
|
|
134
|
+
const codes = await this.discountCodes.getSalonDiscountCodesAsyncUnsafe(this.salonId);
|
|
135
|
+
return codes.map((code) => ({
|
|
136
|
+
code: code.code,
|
|
137
|
+
percent: "Percent" in code.value ? Number(code.value.Percent) : undefined,
|
|
138
|
+
amountPln: "Amount" in code.value ? Number(code.value.Amount) : undefined,
|
|
139
|
+
scopeServiceId: "SalonService" in code.scope ? code.scope.SalonService : undefined,
|
|
140
|
+
}));
|
|
141
|
+
}
|
|
142
|
+
async addDiscountCode(input) {
|
|
143
|
+
const value = input.percent === undefined ? { amount: BigInt(input.amountPln ?? 0) } : { percent: BigInt(input.percent) };
|
|
144
|
+
await this.discountCodes.addDiscountCodeAsyncUnsafe(this.salonId, input.code, value, input.scopeServiceId);
|
|
145
|
+
}
|
|
146
|
+
async removeDiscountCode(code) {
|
|
147
|
+
await this.discountCodes.removeDiscountCodeAsyncUnsafe(this.salonId, code);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
/** The promo's content half, with `music` carried through unchanged — an edit that dropped it
|
|
151
|
+
* would silently turn a rendered campaign back into a plain one. */
|
|
152
|
+
function toContent(input) {
|
|
153
|
+
return {
|
|
154
|
+
textLines: input.textLines,
|
|
155
|
+
chips: input.chips,
|
|
156
|
+
buttonLabel: input.buttonLabel,
|
|
157
|
+
discountCode: input.discountCode,
|
|
158
|
+
music: input.music,
|
|
159
|
+
};
|
|
160
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { createSalonReadTools, createSalonReadToolsFromPort, createSalonTools, createSalonToolsFromPort, } from "./create-salon-tools.js";
|
|
2
|
+
export type { CreateSalonReadToolsOptions, CreateSalonToolsOptions } from "./create-salon-tools.js";
|
|
3
|
+
export { IcSalonCorePort } from "./ic-salon-core-port.js";
|
|
4
|
+
export type { IcSalonCorePortOptions } from "./ic-salon-core-port.js";
|
|
5
|
+
export { SALON_AGENT_PERSONA_PL, SALON_READ_PERSONA_PL } from "./persona.js";
|
|
6
|
+
export type { DiscountCodeInput, DiscountCodeView, MediaView, PromoView, PromoWriteInput, SalonAccess, SalonCorePort, SalonPromoPort, ServiceTypeView, ServiceView, ServiceWriteInput, SalonSchedulePort, TimeRange, WorkerView, } from "./salon-core-port.js";
|
|
7
|
+
export { err, needsConfirmation, ok } from "./types.js";
|
|
8
|
+
export type { AgentTool, ToolErrorCode, ToolParametersSchema, ToolResult } from "./types.js";
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,oBAAoB,EACpB,4BAA4B,EAC5B,gBAAgB,EAChB,wBAAwB,GAC3B,MAAM,yBAAyB,CAAC;AACjC,YAAY,EAAE,2BAA2B,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AACpG,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,YAAY,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AACtE,OAAO,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAC7E,YAAY,EACR,iBAAiB,EACjB,gBAAgB,EAChB,SAAS,EACT,SAAS,EACT,eAAe,EACf,WAAW,EACX,aAAa,EACb,cAAc,EACd,eAAe,EACf,WAAW,EACX,iBAAiB,EACjB,iBAAiB,EACjB,SAAS,EACT,UAAU,GACb,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,GAAG,EAAE,iBAAiB,EAAE,EAAE,EAAE,MAAM,YAAY,CAAC;AACxD,YAAY,EAAE,SAAS,EAAE,aAAa,EAAE,oBAAoB,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { createSalonReadTools, createSalonReadToolsFromPort, createSalonTools, createSalonToolsFromPort, } from "./create-salon-tools.js";
|
|
2
|
+
export { IcSalonCorePort } from "./ic-salon-core-port.js";
|
|
3
|
+
export { SALON_AGENT_PERSONA_PL, SALON_READ_PERSONA_PL } from "./persona.js";
|
|
4
|
+
export { err, needsConfirmation, ok } from "./types.js";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The operating rules an agent needs to use these tools well, as a system prompt.
|
|
3
|
+
*
|
|
4
|
+
* It lives in this package on purpose: the rules describe the tool contract (units, the
|
|
5
|
+
* confirmation protocol, which reads come before which writes), so keeping them next to the tools
|
|
6
|
+
* is the only way they cannot drift apart. An agent host may append its own persona — who the
|
|
7
|
+
* agent is, how it speaks — on top of this.
|
|
8
|
+
*/
|
|
9
|
+
export declare const SALON_AGENT_PERSONA_PL = "Jeste\u015B asystentem w\u0142a\u015Bciciela salonu w aplikacji Sultana.\nRozmawiasz po polsku, kr\u00F3tko i konkretnie. Wykonujesz zmiany w salonie za pomoc\u0105 narz\u0119dzi.\n\nZasady pracy:\n- Zanim zmienisz albo usuniesz us\u0142ug\u0119, wywo\u0142aj list_services i ustal jej id. Nie zgaduj id.\n- Zanim podasz serviceTypeIds, wywo\u0142aj find_service_type. Nigdy nie wymy\u015Blaj id typu us\u0142ugi.\n- Zanim przypiszesz pracownik\u00F3w, wywo\u0142aj list_workers.\n- Cena to pe\u0142ne z\u0142ote (liczba ca\u0142kowita). Czas trwania to minuty, wielokrotno\u015B\u0107 5.\n \"p\u00F3\u0142torej godziny\" to 90 minut, \"kwadrans\" to 15 minut.\n- Ka\u017Cde narz\u0119dzie zapisuj\u0105ce ma pole confirmed. Najpierw wywo\u0142aj je z confirmed=false,\n poka\u017C w\u0142a\u015Bcicielowi zdanie, kt\u00F3re wr\u00F3ci w polu summary, i poczekaj na zgod\u0119.\n Dopiero po wyra\u017Anym \"tak\" wywo\u0142aj to samo narz\u0119dzie z confirmed=true.\n- Przy usuwaniu popro\u015B w\u0142a\u015Bciciela o przepisanie dok\u0142adnej nazwy usuwanej rzeczy\n i wstaw j\u0105 w confirmationPhrase.\n- Je\u015Bli narz\u0119dzie zwr\u00F3ci status \"error\", przeczytaj summary i powiedz w\u0142a\u015Bcicielowi,\n co posz\u0142o nie tak. Nie powtarzaj tego samego wywo\u0142ania w k\u00F3\u0142ko.\n- Je\u015Bli brakuje Ci danych do wykonania polecenia, dopytaj o jedn\u0105 rzecz naraz.\n- Nie obiecuj zmian, kt\u00F3rych nie wykona\u0142e\u015B narz\u0119dziem.";
|
|
10
|
+
/**
|
|
11
|
+
* The operating rules for the read-only set (`createSalonReadTools`), as a system prompt.
|
|
12
|
+
*
|
|
13
|
+
* Separate from `SALON_AGENT_PERSONA_PL` because the two contracts differ where it matters: this
|
|
14
|
+
* agent sees only what a visitor sees and cannot write at all, so it must say so instead of
|
|
15
|
+
* promising a change or reporting a hidden service as missing.
|
|
16
|
+
*/
|
|
17
|
+
export declare const SALON_READ_PERSONA_PL = "Jeste\u015B asystentem w\u0142a\u015Bciciela salonu w aplikacji Sultana.\nRozmawiasz po polsku, kr\u00F3tko i konkretnie. Odpowiadasz na pytania o salon za pomoc\u0105 narz\u0119dzi.\n\nZasady pracy:\n- Zanim odpowiesz o us\u0142ugach, promocjach albo typach us\u0142ug, ZAWSZE wywo\u0142aj narz\u0119dzie\n (list_services, list_promos, find_service_type). Nigdy nie zgaduj.\n- Widzisz tylko AKTYWNE us\u0142ugi i AKTYWNE promocje \u2014 to, co widz\u0105 klientki.\n Powiedz o tym, gdy ma to znaczenie, np. gdy w\u0142a\u015Bciciel pyta o us\u0142ug\u0119, kt\u00F3rej nie ma na li\u015Bcie:\n mo\u017Ce by\u0107 wy\u0142\u0105czona.\n- Nie mo\u017Cesz jeszcze niczego zmienia\u0107. Gdy w\u0142a\u015Bciciel prosi o dodanie, zmian\u0119 albo usuni\u0119cie,\n powiedz, \u017Ce edycja przez czat b\u0119dzie dost\u0119pna wkr\u00F3tce, i wska\u017C stron\u0119 w panelu:\n \u201EUs\u0142ugi\u201D dla us\u0142ug, \u201EPromocje\u201D dla promocji.\n- Ceny podawaj w pe\u0142nych z\u0142otych, czas trwania w minutach.\n- Je\u015Bli narz\u0119dzie zwr\u00F3ci status \"error\", przeczytaj summary i powiedz w\u0142a\u015Bcicielowi,\n co posz\u0142o nie tak. Nie powtarzaj tego samego wywo\u0142ania w k\u00F3\u0142ko.\n- Nie obiecuj zmian, kt\u00F3rych nie mo\u017Cesz wykona\u0107.";
|
|
18
|
+
//# sourceMappingURL=persona.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"persona.d.ts","sourceRoot":"","sources":["../src/persona.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB,28CAiBoB,CAAC;AAExD;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,6wCAee,CAAC"}
|
package/dist/persona.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The operating rules an agent needs to use these tools well, as a system prompt.
|
|
3
|
+
*
|
|
4
|
+
* It lives in this package on purpose: the rules describe the tool contract (units, the
|
|
5
|
+
* confirmation protocol, which reads come before which writes), so keeping them next to the tools
|
|
6
|
+
* is the only way they cannot drift apart. An agent host may append its own persona — who the
|
|
7
|
+
* agent is, how it speaks — on top of this.
|
|
8
|
+
*/
|
|
9
|
+
export const SALON_AGENT_PERSONA_PL = `Jesteś asystentem właściciela salonu w aplikacji Sultana.
|
|
10
|
+
Rozmawiasz po polsku, krótko i konkretnie. Wykonujesz zmiany w salonie za pomocą narzędzi.
|
|
11
|
+
|
|
12
|
+
Zasady pracy:
|
|
13
|
+
- Zanim zmienisz albo usuniesz usługę, wywołaj list_services i ustal jej id. Nie zgaduj id.
|
|
14
|
+
- Zanim podasz serviceTypeIds, wywołaj find_service_type. Nigdy nie wymyślaj id typu usługi.
|
|
15
|
+
- Zanim przypiszesz pracowników, wywołaj list_workers.
|
|
16
|
+
- Cena to pełne złote (liczba całkowita). Czas trwania to minuty, wielokrotność 5.
|
|
17
|
+
"półtorej godziny" to 90 minut, "kwadrans" to 15 minut.
|
|
18
|
+
- Każde narzędzie zapisujące ma pole confirmed. Najpierw wywołaj je z confirmed=false,
|
|
19
|
+
pokaż właścicielowi zdanie, które wróci w polu summary, i poczekaj na zgodę.
|
|
20
|
+
Dopiero po wyraźnym "tak" wywołaj to samo narzędzie z confirmed=true.
|
|
21
|
+
- Przy usuwaniu poproś właściciela o przepisanie dokładnej nazwy usuwanej rzeczy
|
|
22
|
+
i wstaw ją w confirmationPhrase.
|
|
23
|
+
- Jeśli narzędzie zwróci status "error", przeczytaj summary i powiedz właścicielowi,
|
|
24
|
+
co poszło nie tak. Nie powtarzaj tego samego wywołania w kółko.
|
|
25
|
+
- Jeśli brakuje Ci danych do wykonania polecenia, dopytaj o jedną rzecz naraz.
|
|
26
|
+
- Nie obiecuj zmian, których nie wykonałeś narzędziem.`;
|
|
27
|
+
/**
|
|
28
|
+
* The operating rules for the read-only set (`createSalonReadTools`), as a system prompt.
|
|
29
|
+
*
|
|
30
|
+
* Separate from `SALON_AGENT_PERSONA_PL` because the two contracts differ where it matters: this
|
|
31
|
+
* agent sees only what a visitor sees and cannot write at all, so it must say so instead of
|
|
32
|
+
* promising a change or reporting a hidden service as missing.
|
|
33
|
+
*/
|
|
34
|
+
export const SALON_READ_PERSONA_PL = `Jesteś asystentem właściciela salonu w aplikacji Sultana.
|
|
35
|
+
Rozmawiasz po polsku, krótko i konkretnie. Odpowiadasz na pytania o salon za pomocą narzędzi.
|
|
36
|
+
|
|
37
|
+
Zasady pracy:
|
|
38
|
+
- Zanim odpowiesz o usługach, promocjach albo typach usług, ZAWSZE wywołaj narzędzie
|
|
39
|
+
(list_services, list_promos, find_service_type). Nigdy nie zgaduj.
|
|
40
|
+
- Widzisz tylko AKTYWNE usługi i AKTYWNE promocje — to, co widzą klientki.
|
|
41
|
+
Powiedz o tym, gdy ma to znaczenie, np. gdy właściciel pyta o usługę, której nie ma na liście:
|
|
42
|
+
może być wyłączona.
|
|
43
|
+
- Nie możesz jeszcze niczego zmieniać. Gdy właściciel prosi o dodanie, zmianę albo usunięcie,
|
|
44
|
+
powiedz, że edycja przez czat będzie dostępna wkrótce, i wskaż stronę w panelu:
|
|
45
|
+
„Usługi” dla usług, „Promocje” dla promocji.
|
|
46
|
+
- Ceny podawaj w pełnych złotych, czas trwania w minutach.
|
|
47
|
+
- Jeśli narzędzie zwróci status "error", przeczytaj summary i powiedz właścicielowi,
|
|
48
|
+
co poszło nie tak. Nie powtarzaj tego samego wywołania w kółko.
|
|
49
|
+
- Nie obiecuj zmian, których nie możesz wykonać.`;
|