@pliuz/sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Bare PliuzClient — create approval, poll, report execution by hand.
3
+ *
4
+ * Use this when you need full control over the flow. For 99% of cases,
5
+ * use gated() — see gated-basic.ts.
6
+ *
7
+ * Run:
8
+ * export PLIUZ_API_KEY=pli_live_...
9
+ * npx tsx examples/basic.ts
10
+ */
11
+
12
+ import { PliuzClient, PliuzPolicyError } from '@pliuz/sdk'
13
+
14
+ async function main(): Promise<void> {
15
+ const pliuz = new PliuzClient()
16
+
17
+ // 1. Submit the approval request.
18
+ let created
19
+ try {
20
+ created = await pliuz.createApproval({
21
+ tool_name: 'example_tool',
22
+ tool_args: { foo: 'bar', value: 42 },
23
+ context_messages: ['Triggered from examples/basic.ts'],
24
+ idempotency_key: 'basic-example-ts-001', // safe to retry
25
+ })
26
+ } catch (e) {
27
+ if (e instanceof PliuzPolicyError) {
28
+ console.log(`No policy matched: ${e.message}`)
29
+ console.log("Create a policy targeting tool_name='example_tool' in the dashboard.")
30
+ return
31
+ }
32
+ throw e
33
+ }
34
+
35
+ console.log(`Created approval ${created.id} (status=${created.status})`)
36
+
37
+ // 2. Poll if pending. gated() does this for you.
38
+ if (created.status === 'pending') {
39
+ for (let attempt = 0; attempt < 60; attempt++) {
40
+ await new Promise((resolve) => setTimeout(resolve, 2000))
41
+ const approval = await pliuz.getApproval(created.id)
42
+ console.log(` poll #${attempt}: status=${approval.status}`)
43
+ if (approval.status !== 'pending') break
44
+ }
45
+ }
46
+
47
+ // 3. Read final state.
48
+ const final = await pliuz.getApproval(created.id)
49
+ if (final.status !== 'approved') {
50
+ console.log(`Not approved: ${final.status} (${final.decision_reason})`)
51
+ return
52
+ }
53
+
54
+ // 4. Do the work.
55
+ console.log('Approved — running the gated action.')
56
+ const result = { didSomething: true }
57
+
58
+ // 5. Report execution.
59
+ const report = await pliuz.reportExecution(final.id, {
60
+ status: 'success',
61
+ latency_ms: 42,
62
+ target_response_excerpt: JSON.stringify(result),
63
+ })
64
+ console.log(`Reported execution: seq=${report.event_seq}`)
65
+ }
66
+
67
+ main().catch((e) => {
68
+ console.error('Error:', e)
69
+ process.exit(1)
70
+ })
@@ -0,0 +1,62 @@
1
+ /**
2
+ * gated() — the headline API. Wrap a function, call it normally.
3
+ *
4
+ * The wrapper handles create + poll + execute + report internally.
5
+ *
6
+ * Run:
7
+ * export PLIUZ_API_KEY=pli_live_...
8
+ * npx tsx examples/gated-basic.ts
9
+ */
10
+
11
+ import { gated, PliuzRejectedError } from '@pliuz/sdk'
12
+
13
+ interface Customer {
14
+ id: string
15
+ ssn: string
16
+ dob: string
17
+ email: string
18
+ }
19
+
20
+ const issueRefund = gated(
21
+ {
22
+ policy: 'refund',
23
+ redact: ['customer.ssn', 'customer.dob'], // never leave plaintext
24
+ timeoutMs: 120_000, // give the human 2 minutes
25
+ // toolArgs maps positional args into a named dict for Pliuz audit log
26
+ toolArgs: (customerId: string, amountCents: number, customer: Customer) => ({
27
+ customer_id: customerId,
28
+ amount_cents: amountCents,
29
+ customer,
30
+ }),
31
+ },
32
+ async (customerId: string, amountCents: number, _customer: Customer) => {
33
+ console.log(` → executing refund for ${customerId} ($${(amountCents / 100).toFixed(2)})`)
34
+ // In a real app: await stripe.refunds.create({ customer: customerId, amount: amountCents })
35
+ return { refund_id: 're_fake', amount: amountCents }
36
+ },
37
+ )
38
+
39
+ async function main(): Promise<void> {
40
+ const customer: Customer = {
41
+ id: 'cus_123',
42
+ ssn: '123-45-6789', // ← redacted before send
43
+ dob: '1990-01-01', // ← redacted before send
44
+ email: 'customer@example.com', // ← visible to approver
45
+ }
46
+
47
+ try {
48
+ const result = await issueRefund('cus_123', 5000, customer)
49
+ console.log(`Done: ${JSON.stringify(result)}`)
50
+ } catch (e) {
51
+ if (e instanceof PliuzRejectedError) {
52
+ console.log(`Rejected by approver: ${e.reason}`)
53
+ return
54
+ }
55
+ throw e
56
+ }
57
+ }
58
+
59
+ main().catch((e) => {
60
+ console.error('Error:', e)
61
+ process.exit(1)
62
+ })
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Vercel AI SDK agent with a gated tool — drop-in for any AI SDK agent.
3
+ *
4
+ * gatedTool() wraps tool({...}) from `ai`. The LLM sees the tool
5
+ * identically (same description, parameters); only execute() is gated.
6
+ *
7
+ * Install:
8
+ * npm install @pliuz/sdk ai @ai-sdk/openai zod
9
+ *
10
+ * Run:
11
+ * export PLIUZ_API_KEY=pli_live_...
12
+ * export OPENAI_API_KEY=sk-...
13
+ * npx tsx examples/vercel-ai-agent.ts
14
+ */
15
+
16
+ import { tool, generateText } from 'ai'
17
+ import { openai } from '@ai-sdk/openai'
18
+ import { z } from 'zod'
19
+ import { gatedTool } from '@pliuz/sdk/adapters/ai'
20
+
21
+ const issueRefund = gatedTool(
22
+ {
23
+ policy: 'refund',
24
+ redact: ['customer_id'], // strip from audit log
25
+ },
26
+ tool({
27
+ description: 'Issue a refund to a customer. Requires human approval.',
28
+ parameters: z.object({
29
+ customer_id: z.string().describe('The Stripe customer ID'),
30
+ amount_cents: z.number().int().positive().describe('Amount to refund in cents'),
31
+ }),
32
+ execute: async ({ customer_id, amount_cents }) => {
33
+ // In a real app: stripe.refunds.create({ customer: customer_id, amount: amount_cents })
34
+ return { refund_id: 're_fake', customer: customer_id, amount: amount_cents }
35
+ },
36
+ }),
37
+ )
38
+
39
+ const getCustomerEmail = tool({
40
+ description: "Look up a customer's email by ID. Safe to run without approval.",
41
+ parameters: z.object({
42
+ customer_id: z.string(),
43
+ }),
44
+ execute: async ({ customer_id }) => {
45
+ void customer_id
46
+ return { email: 'customer@example.com' }
47
+ },
48
+ })
49
+
50
+ async function main(): Promise<void> {
51
+ const { text, toolCalls } = await generateText({
52
+ model: openai('gpt-4o'),
53
+ tools: { issueRefund, getCustomerEmail },
54
+ prompt: 'Refund customer cus_123 for $50. Look up their email first.',
55
+ maxSteps: 5,
56
+ })
57
+
58
+ console.log('Final response:', text)
59
+ console.log('Tool calls:', JSON.stringify(toolCalls, null, 2))
60
+ }
61
+
62
+ main().catch((e) => {
63
+ console.error('Error:', e)
64
+ process.exit(1)
65
+ })
package/package.json ADDED
@@ -0,0 +1,91 @@
1
+ {
2
+ "name": "@pliuz/sdk",
3
+ "version": "0.1.0",
4
+ "description": "Human-in-the-loop approval gates for AI agents — pause function calls for human review, then resume or abort.",
5
+ "keywords": [
6
+ "ai",
7
+ "agents",
8
+ "approval",
9
+ "audit",
10
+ "human-in-the-loop",
11
+ "llm",
12
+ "guardrails",
13
+ "vercel-ai-sdk"
14
+ ],
15
+ "homepage": "https://pliuz.dev",
16
+ "bugs": "https://github.com/mwhitex/pliuz/issues",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/mwhitex/pliuz.git",
20
+ "directory": "sdk-typescript"
21
+ },
22
+ "license": "Apache-2.0",
23
+ "author": "Pliuz",
24
+ "type": "module",
25
+ "main": "./dist/index.cjs",
26
+ "module": "./dist/index.js",
27
+ "types": "./dist/index.d.ts",
28
+ "exports": {
29
+ ".": {
30
+ "types": "./dist/index.d.ts",
31
+ "import": "./dist/index.js",
32
+ "require": "./dist/index.cjs"
33
+ },
34
+ "./adapters/ai": {
35
+ "types": "./dist/adapters/ai.d.ts",
36
+ "import": "./dist/adapters/ai.js",
37
+ "require": "./dist/adapters/ai.cjs"
38
+ }
39
+ },
40
+ "files": [
41
+ "dist",
42
+ "examples",
43
+ "README.md",
44
+ "CHANGELOG.md",
45
+ "LICENSE"
46
+ ],
47
+ "engines": {
48
+ "node": ">=18.17.0"
49
+ },
50
+ "publishConfig": {
51
+ "access": "public",
52
+ "registry": "https://registry.npmjs.org/"
53
+ },
54
+ "scripts": {
55
+ "build": "tsup",
56
+ "dev": "tsup --watch",
57
+ "test": "vitest run",
58
+ "test:watch": "vitest",
59
+ "test:coverage": "vitest run --coverage",
60
+ "typecheck": "tsc --noEmit",
61
+ "lint": "echo 'ESLint config pending — tracked as post-0.1.0 improvement' && exit 0",
62
+ "format": "prettier --write src test",
63
+ "clean": "rm -rf dist",
64
+ "prepublishOnly": "npm run clean && npm run typecheck && npm run test && npm run build"
65
+ },
66
+ "peerDependencies": {
67
+ "ai": ">=4.0.0"
68
+ },
69
+ "peerDependenciesMeta": {
70
+ "ai": {
71
+ "optional": true
72
+ }
73
+ },
74
+ "devDependencies": {
75
+ "@types/node": "^22.0.0",
76
+ "@types/react": "^19.0.0",
77
+ "@typescript-eslint/eslint-plugin": "^8.0.0",
78
+ "@typescript-eslint/parser": "^8.0.0",
79
+ "@vitest/coverage-v8": "^2.1.0",
80
+ "ai": "^4.0.0",
81
+ "eslint": "^9.0.0",
82
+ "msw": "^2.6.0",
83
+ "prettier": "^3.3.0",
84
+ "react": "^19.0.0",
85
+ "tsup": "^8.3.0",
86
+ "typescript": "^5.7.0",
87
+ "vitest": "^2.1.0",
88
+ "yaml": "^2.9.0",
89
+ "zod": "^3.23.0"
90
+ }
91
+ }