@plasm_lang/vercel-agent 0.3.77 → 0.3.80
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 +9 -2
- package/agent/instructions.md +1 -2
- package/bin/plasm-agent.mjs +5 -7
- package/package.json +29 -19
- package/scripts/build-stubs.ts +27 -0
- package/scripts/plasm-cli.ts +162 -0
- package/scripts/register-plasm-loader.mjs +6 -0
- package/scripts/run-plasm-cli.mjs +41 -0
- package/src/cli/init-scaffold.ts +198 -0
- package/src/cli/init.ts +20 -208
- package/src/cli/nitro-dev.ts +69 -0
- package/src/cli/node-dev-imports.ts +14 -0
- package/src/engine/create-host-transport.ts +7 -0
- package/src/index.ts +1 -1
- package/src/package-version.ts +14 -0
- package/src/project-info.ts +2 -1
- package/src/stubs/catalog-client.ts +2 -2
- package/src/tools/harness-tools.ts +42 -41
- package/src/tools/plasm-tools.ts +64 -55
- package/src/tools/tool-input.ts +9 -0
- package/templates/mcp-radar/.vercelignore +3 -0
- package/templates/mcp-radar/README.md +139 -0
- package/templates/mcp-radar/agent/agent.ts +38 -0
- package/templates/mcp-radar/agent/catalogs/hackernews/README.md +23 -0
- package/templates/mcp-radar/agent/catalogs/hackernews/domain.yaml +329 -0
- package/templates/mcp-radar/agent/catalogs/hackernews/eval/cases.yaml +112 -0
- package/templates/mcp-radar/agent/catalogs/hackernews/mappings.yaml +129 -0
- package/templates/mcp-radar/agent/catalogs/tavily/README.md +218 -0
- package/templates/mcp-radar/agent/catalogs/tavily/domain.yaml +373 -0
- package/templates/mcp-radar/agent/catalogs/tavily/eval/cases.yaml +56 -0
- package/templates/mcp-radar/agent/catalogs/tavily/eval/google-gemma-3-4b-it.latest.human.txt +67 -0
- package/templates/mcp-radar/agent/catalogs/tavily/eval/google-gemma-3-4b-it.latest.json +363 -0
- package/templates/mcp-radar/agent/catalogs/tavily/mappings.yaml +171 -0
- package/templates/mcp-radar/agent/channels/mcp-radar.ts +85 -0
- package/templates/mcp-radar/agent/hooks/proof-audit.ts +10 -0
- package/templates/mcp-radar/agent/instructions.md +24 -0
- package/templates/mcp-radar/agent/research/mcp-innovations-proof.md +4 -0
- package/templates/mcp-radar/agent/schedules/mcp-radar-scan.ts +14 -0
- package/templates/mcp-radar/agent/skills/mcp-proof-format.md +19 -0
- package/templates/mcp-radar/api/[[...path]].ts +23 -0
- package/templates/mcp-radar/evals/mcp-radar-discover.eval.ts +14 -0
- package/templates/mcp-radar/lib/proof-store.ts +265 -0
- package/templates/mcp-radar/lib/run-radar.ts +214 -0
- package/templates/mcp-radar/nitro.config.ts +17 -0
- package/templates/mcp-radar/package.json +14 -0
- package/templates/mcp-radar/public/index.html +10 -0
- package/templates/mcp-radar/routes/[...path].ts +29 -0
- package/templates/mcp-radar/scripts/run-evals.ts +46 -0
- package/templates/mcp-radar/scripts/smoke-channel.ts +66 -0
- package/templates/mcp-radar/vercel.json +15 -0
- package/templates/scaffold/.vercelignore +3 -0
- package/templates/scaffold/api/[[...path]].ts +23 -0
- package/templates/scaffold/nitro.config.ts +17 -0
- package/templates/scaffold/public/index.html +10 -0
- package/templates/scaffold/public/index.mcp-radar.html +10 -0
- package/templates/scaffold/routes/[...path].ts +29 -0
- package/templates/scaffold/vercel.default.json +4 -0
- package/templates/scaffold/vercel.mcp-radar.json +15 -0
package/src/tools/plasm-tools.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { tool } from "ai";
|
|
1
|
+
import { tool, type ToolSet } from "ai";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
|
|
4
4
|
import type { AgentRuntime } from "../runtime/agent-runtime.js";
|
|
@@ -8,89 +8,98 @@ import {
|
|
|
8
8
|
PLASM_RUN_TOOL_DESCRIPTION,
|
|
9
9
|
PLASM_TOOL_DESCRIPTION,
|
|
10
10
|
} from "./descriptions.js";
|
|
11
|
+
import { toolInput } from "./tool-input.js";
|
|
11
12
|
|
|
12
13
|
const seedSchema = z.object({
|
|
13
14
|
api: z.string().describe("Registry entry_id / catalog api"),
|
|
14
15
|
entity: z.string().describe("CGS entity name from discovery or prior knowledge"),
|
|
15
16
|
});
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
const discoverInputSchema = z.object({
|
|
19
|
+
intent: z
|
|
20
|
+
.string()
|
|
21
|
+
.min(1)
|
|
22
|
+
.describe(
|
|
23
|
+
"One plain-language task description for the whole user goal. Returns catalog api/entity picks — not program symbols.",
|
|
24
|
+
),
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const plasmContextInputSchema = z.object({
|
|
28
|
+
intent: z
|
|
29
|
+
.string()
|
|
30
|
+
.min(1)
|
|
31
|
+
.describe(
|
|
32
|
+
"Stable string for one user goal (same value every turn for that goal — do not rotate per message).",
|
|
33
|
+
),
|
|
34
|
+
seeds: z
|
|
35
|
+
.array(seedSchema)
|
|
36
|
+
.min(1)
|
|
37
|
+
.describe("Non-empty array of {api, entity} capability picks"),
|
|
38
|
+
ranked_capabilities: z
|
|
39
|
+
.array(z.string())
|
|
40
|
+
.nullable()
|
|
41
|
+
.optional()
|
|
42
|
+
.describe(
|
|
43
|
+
"Optional capability wire names from discover_capabilities. Omit on expand; null or [] clears.",
|
|
44
|
+
),
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const plasmInputSchema = z.object({
|
|
48
|
+
logical_session_ref: z
|
|
49
|
+
.string()
|
|
50
|
+
.describe("Same logical_session_ref returned by plasm_context"),
|
|
51
|
+
program: z
|
|
52
|
+
.string()
|
|
53
|
+
.min(1)
|
|
54
|
+
.describe("Plasm source program using e#/m#/p#/r# from the session teaching TSV"),
|
|
55
|
+
reasoning: z
|
|
56
|
+
.string()
|
|
57
|
+
.optional()
|
|
58
|
+
.describe("Optional short note explaining the intent of this call"),
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const plasmRunInputSchema = z.object({
|
|
62
|
+
logical_session_ref: z
|
|
63
|
+
.string()
|
|
64
|
+
.describe("Same logical_session_ref returned by plasm_context"),
|
|
65
|
+
plan_commit_ref: z
|
|
66
|
+
.string()
|
|
67
|
+
.describe("Executable plan token (pcN) from a prior plasm dry-run"),
|
|
68
|
+
reasoning: z
|
|
69
|
+
.string()
|
|
70
|
+
.optional()
|
|
71
|
+
.describe("Optional short note explaining the intent of this call"),
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
export function createPlasmTools(runtime: AgentRuntime): ToolSet {
|
|
18
75
|
return {
|
|
19
76
|
discover_capabilities: tool({
|
|
20
77
|
description: DISCOVER_TOOL_DESCRIPTION,
|
|
21
|
-
inputSchema:
|
|
22
|
-
intent: z
|
|
23
|
-
.string()
|
|
24
|
-
.min(1)
|
|
25
|
-
.describe(
|
|
26
|
-
"One plain-language task description for the whole user goal. Returns catalog api/entity picks — not program symbols.",
|
|
27
|
-
),
|
|
28
|
-
}),
|
|
78
|
+
inputSchema: toolInput(discoverInputSchema),
|
|
29
79
|
execute: async ({ intent }) => runtime.discoverCapabilities({ intent }),
|
|
30
80
|
}),
|
|
31
81
|
|
|
32
82
|
plasm_context: tool({
|
|
33
83
|
description: PLASM_CONTEXT_TOOL_DESCRIPTION,
|
|
34
|
-
inputSchema:
|
|
35
|
-
intent: z
|
|
36
|
-
.string()
|
|
37
|
-
.min(1)
|
|
38
|
-
.describe(
|
|
39
|
-
"Stable string for one user goal (same value every turn for that goal — do not rotate per message).",
|
|
40
|
-
),
|
|
41
|
-
seeds: z
|
|
42
|
-
.array(seedSchema)
|
|
43
|
-
.min(1)
|
|
44
|
-
.describe("Non-empty array of {api, entity} capability picks"),
|
|
45
|
-
ranked_capabilities: z
|
|
46
|
-
.array(z.string())
|
|
47
|
-
.nullable()
|
|
48
|
-
.optional()
|
|
49
|
-
.describe(
|
|
50
|
-
"Optional capability wire names from discover_capabilities. Omit on expand; null or [] clears.",
|
|
51
|
-
),
|
|
52
|
-
}),
|
|
84
|
+
inputSchema: toolInput(plasmContextInputSchema),
|
|
53
85
|
execute: async ({ intent, seeds, ranked_capabilities }) =>
|
|
54
86
|
runtime.plasmContext({
|
|
55
87
|
intent,
|
|
56
|
-
seeds
|
|
88
|
+
seeds: seeds as Array<{ api: string; entity: string }>,
|
|
57
89
|
rankedCapabilities: ranked_capabilities,
|
|
58
90
|
}),
|
|
59
91
|
}),
|
|
60
92
|
|
|
61
93
|
plasm: tool({
|
|
62
94
|
description: PLASM_TOOL_DESCRIPTION,
|
|
63
|
-
inputSchema:
|
|
64
|
-
logical_session_ref: z
|
|
65
|
-
.string()
|
|
66
|
-
.describe("Same logical_session_ref returned by plasm_context"),
|
|
67
|
-
program: z
|
|
68
|
-
.string()
|
|
69
|
-
.min(1)
|
|
70
|
-
.describe("Plasm source program using e#/m#/p#/r# from the session teaching TSV"),
|
|
71
|
-
reasoning: z
|
|
72
|
-
.string()
|
|
73
|
-
.optional()
|
|
74
|
-
.describe("Optional short note explaining the intent of this call"),
|
|
75
|
-
}),
|
|
95
|
+
inputSchema: toolInput(plasmInputSchema),
|
|
76
96
|
execute: async ({ logical_session_ref, program, reasoning }) =>
|
|
77
97
|
runtime.plasm({ logicalSessionRef: logical_session_ref, program, reasoning }),
|
|
78
98
|
}),
|
|
79
99
|
|
|
80
100
|
plasm_run: tool({
|
|
81
101
|
description: PLASM_RUN_TOOL_DESCRIPTION,
|
|
82
|
-
inputSchema:
|
|
83
|
-
logical_session_ref: z
|
|
84
|
-
.string()
|
|
85
|
-
.describe("Same logical_session_ref returned by plasm_context"),
|
|
86
|
-
plan_commit_ref: z
|
|
87
|
-
.string()
|
|
88
|
-
.describe("Executable plan token (pcN) from a prior plasm dry-run"),
|
|
89
|
-
reasoning: z
|
|
90
|
-
.string()
|
|
91
|
-
.optional()
|
|
92
|
-
.describe("Optional short note explaining the intent of this call"),
|
|
93
|
-
}),
|
|
102
|
+
inputSchema: toolInput(plasmRunInputSchema),
|
|
94
103
|
execute: async ({ logical_session_ref, plan_commit_ref, reasoning }) =>
|
|
95
104
|
runtime.plasmRun({
|
|
96
105
|
logicalSessionRef: logical_session_ref,
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { zodSchema, type FlexibleSchema } from "ai";
|
|
2
|
+
import type { z } from "zod";
|
|
3
|
+
|
|
4
|
+
/** Wrap Zod for AI SDK `tool()` — avoids TS2589 deep instantiation during Vercel typecheck. */
|
|
5
|
+
export function toolInput<T extends z.ZodTypeAny>(
|
|
6
|
+
schema: T,
|
|
7
|
+
): FlexibleSchema<z.infer<T>> {
|
|
8
|
+
return zodSchema(schema);
|
|
9
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# MCP Radar Agent
|
|
2
|
+
|
|
3
|
+
Event-driven research example for [`@plasm_lang/vercel-agent`](../../plasm-oss/packages/plasm-agent/): watch Hacker News for **MCP** innovation signals, corroborate with **Tavily**, and append structured entries to a maintained proof log.
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
Demonstrates Plasm’s **dual surface**:
|
|
8
|
+
|
|
9
|
+
- **Code surface** — channel/schedule handlers preflight HN via typed stubs and own proof persistence
|
|
10
|
+
- **Model surface** — federated `plasm_context` (hackernews + tavily) → `plasm` → `plasm_run` for synthesis
|
|
11
|
+
|
|
12
|
+
## Bootstrap from CLI
|
|
13
|
+
|
|
14
|
+
Scaffold a fresh copy (verified by `npm run smoke:bootstrap` in the framework package):
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
mkdir /tmp/my-radar && cd /tmp/my-radar
|
|
18
|
+
plasm-agent init --template mcp-radar .
|
|
19
|
+
npm install
|
|
20
|
+
plasm-agent build
|
|
21
|
+
npm run smoke:channel
|
|
22
|
+
plasm-agent dev
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The canonical template source is this directory (`examples/mcp-radar-agent/`).
|
|
26
|
+
|
|
27
|
+
## Deploy to Vercel
|
|
28
|
+
|
|
29
|
+
Low-friction production path (eve-aligned): `vercel.json` + catch-all `api/[[...path]].ts` mounts the same routes as local dev (channels, cron, `/plasm/v1/info`).
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
cd examples/mcp-radar-agent # or your bootstrapped project
|
|
33
|
+
plasm-agent link
|
|
34
|
+
plasm-agent build
|
|
35
|
+
vercel env pull .env.local # AI_GATEWAY_API_KEY, CRON_SECRET, KV, Blob
|
|
36
|
+
vercel deploy
|
|
37
|
+
curl -s "$DEPLOY_URL/channel/mcp-radar/status" | jq .
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
| Variable | Deploy |
|
|
41
|
+
|----------|--------|
|
|
42
|
+
| `AI_GATEWAY_API_KEY` | Required for agent synthesis |
|
|
43
|
+
| `CRON_SECRET` | Required — Vercel Cron hits `/internal/cron/mcp-radar-scan` |
|
|
44
|
+
| `KV_REST_API_URL` + `KV_REST_API_TOKEN` | Durable seen-items + last-run state |
|
|
45
|
+
| `BLOB_READ_WRITE_TOKEN` | Durable proof markdown log |
|
|
46
|
+
| `TAVILY_API_TOKEN` | Optional corroboration |
|
|
47
|
+
|
|
48
|
+
On Vercel, `POST /channel/mcp-radar/run` returns `202` and continues in the background via `waitUntil`. Locally it runs synchronously for smoke tests.
|
|
49
|
+
|
|
50
|
+
Monorepo deploy: set Vercel **Root Directory** to `examples/mcp-radar-agent`.
|
|
51
|
+
|
|
52
|
+
## Setup (monorepo checkout)
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
cd examples/mcp-radar-agent
|
|
56
|
+
npm install
|
|
57
|
+
cp .env.example .env.local
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
| Variable | Required | Role |
|
|
61
|
+
|----------|----------|------|
|
|
62
|
+
| `AI_GATEWAY_API_KEY` | Yes (live runs) | Agent model turns |
|
|
63
|
+
| `TAVILY_API_TOKEN` | Optional | Tavily corroboration; HN-only when unset |
|
|
64
|
+
|
|
65
|
+
Build CGS stubs (symlinked catalogs):
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
npm run build
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Dev
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
npm run build
|
|
75
|
+
npm run dev # Nitro dev server — Vercel routing parity (channels, cron, /plasm/v1/*)
|
|
76
|
+
npm run dev:interactive # optional: in-process server + TUI + sessions + hot reload
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
`plasm-agent dev` starts Nitro by default so channel routes like `/channel/mcp-radar/status` match production.
|
|
80
|
+
|
|
81
|
+
Slash commands in the interactive TUI: `/info`, `/catalogs`, `/new`, `/quit`.
|
|
82
|
+
|
|
83
|
+
## Channel API
|
|
84
|
+
|
|
85
|
+
Start headless dev server (`npm run dev:headless`), then:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
BASE=http://127.0.0.1:3000
|
|
89
|
+
|
|
90
|
+
# Status
|
|
91
|
+
curl -s "$BASE/channel/mcp-radar/status" | jq .
|
|
92
|
+
|
|
93
|
+
# Read proof log (markdown)
|
|
94
|
+
curl -s "$BASE/channel/mcp-radar/proof"
|
|
95
|
+
|
|
96
|
+
# Trigger one scan (dedupes seen HN ids)
|
|
97
|
+
curl -s -X POST "$BASE/channel/mcp-radar/run" -H 'content-type: application/json' -d '{}'
|
|
98
|
+
|
|
99
|
+
# Force scan even when no new ids
|
|
100
|
+
curl -s -X POST "$BASE/channel/mcp-radar/run" -H 'content-type: application/json' -d '{"force":true}'
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Proof artifact (local fs): [`agent/research/mcp-innovations-proof.md`](./agent/research/mcp-innovations-proof.md)
|
|
104
|
+
|
|
105
|
+
Dedupe state (local fs): `agent/.plasm/research/seen-hn-items.json` (gitignored). On Vercel deploy, proof + dedupe use Blob + KV when env vars are set.
|
|
106
|
+
|
|
107
|
+
## Schedule
|
|
108
|
+
|
|
109
|
+
`agent/schedules/mcp-radar-scan.ts` runs every **6 hours** (`0 */6 * * *`). Vercel Cron calls `/internal/cron/mcp-radar-scan` with `Authorization: Bearer $CRON_SECRET`.
|
|
110
|
+
|
|
111
|
+
## Evals
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
npm run eval # requires AI_GATEWAY_API_KEY
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Live eval: `evals/mcp-radar-discover.eval.ts`
|
|
118
|
+
|
|
119
|
+
## Smoke
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
npm run build
|
|
123
|
+
npm run smoke:channel
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Catalogs
|
|
127
|
+
|
|
128
|
+
Symlinked from monorepo `apis/`:
|
|
129
|
+
|
|
130
|
+
- `agent/catalogs/hackernews` → `plasm-oss/apis/hackernews`
|
|
131
|
+
- `agent/catalogs/tavily` → `plasm-oss/apis/tavily`
|
|
132
|
+
|
|
133
|
+
## Stable session intent
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
track MCP innovations from Hacker News and corroborate with Tavily web search
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Federated seeds: `hackernews:Item`, `tavily:SearchResult`
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
createAgentFromProject,
|
|
6
|
+
createProductionHostTransport,
|
|
7
|
+
defineAgent,
|
|
8
|
+
loadAgentEnv,
|
|
9
|
+
} from "@plasm_lang/vercel-agent";
|
|
10
|
+
|
|
11
|
+
loadAgentEnv();
|
|
12
|
+
|
|
13
|
+
const agentRoot = path.dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
|
|
15
|
+
const agentDefinition = defineAgent({
|
|
16
|
+
model: process.env.PLASM_AGENT_MODEL ?? "anthropic/claude-sonnet-4.6",
|
|
17
|
+
compaction: { thresholdPercent: 0.75 },
|
|
18
|
+
modelOptions: { temperature: 0.2 },
|
|
19
|
+
experimental: {
|
|
20
|
+
workflow: { world: { type: "vercel" } },
|
|
21
|
+
skills: true,
|
|
22
|
+
},
|
|
23
|
+
build: {
|
|
24
|
+
externalDependencies: ["@plasm_lang/engine"],
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
export default agentDefinition;
|
|
29
|
+
|
|
30
|
+
export async function createPlasmAgent() {
|
|
31
|
+
return createAgentFromProject(agentDefinition, {
|
|
32
|
+
agentRoot,
|
|
33
|
+
tenantScope: process.env.PLASM_TENANT_SCOPE ?? "mcp-radar",
|
|
34
|
+
maxSteps: 24,
|
|
35
|
+
telemetry: process.env.PLASM_AGENT_TELEMETRY !== "0",
|
|
36
|
+
hostTransport: createProductionHostTransport(),
|
|
37
|
+
});
|
|
38
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Hacker News (Firebase API)
|
|
2
|
+
|
|
3
|
+
Read-only mirror of the public [Hacker News API](https://github.com/HackerNews/API) plus the [Algolia HN search API](https://hn.algolia.com/api) (full-URL CML; same `http_backend` is Firebase for `item_get`). No authentication.
|
|
4
|
+
|
|
5
|
+
## Scope
|
|
6
|
+
|
|
7
|
+
- **item_search** / **item_search_by_date** — Algolia `search` and `search_by_date` (default `tags=story`). Rows use `objectID` as `id`; use **item_get** to load the Firebase item.
|
|
8
|
+
- **item_feed_query** — ordered item ids for `top`, `new`, `best`, `ask`, `show`, or `job` (ids only; use **item_get** to hydrate).
|
|
9
|
+
- **max_item_id_query** — current largest item id (`maxitem.json` returns a bare integer; mapped via `wrap_root_scalar`).
|
|
10
|
+
- **recent_updated_item_query** / **recent_updated_user_query** — live slices from `updates.json` (`items` and `profiles` arrays; profiles are usernames only).
|
|
11
|
+
- **item_get** / **user_get** — full JSON for an item or user. Poll stories expose **`parts`** (option ids) and the **`poll_options`** relation for chaining.
|
|
12
|
+
|
|
13
|
+
## Try it
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
cargo run --bin plasm-repl -- --schema apis/hackernews
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Eval coverage (no LLM):
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
cargo run -p plasm-eval -- coverage --schema apis/hackernews --cases apis/hackernews/eval/cases.yaml
|
|
23
|
+
```
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
version: 2
|
|
2
|
+
auth:
|
|
3
|
+
scheme: none
|
|
4
|
+
http_backend: https://hacker-news.firebaseio.com
|
|
5
|
+
entities:
|
|
6
|
+
Item:
|
|
7
|
+
id_field: id
|
|
8
|
+
description: A Hacker News item (story, comment, job, poll, or poll option). Use item_search (Algolia) to discover ids.
|
|
9
|
+
from text, then item_get for full rows from Firebase. Feed endpoints return only numeric ids; use item_get to load title,
|
|
10
|
+
text, score, and comment trees. Direct replies are listed under kids as further item ids. Poll posts expose poll option
|
|
11
|
+
ids under parts; each option is another Item row.
|
|
12
|
+
fields:
|
|
13
|
+
id:
|
|
14
|
+
required: true
|
|
15
|
+
value_ref: nv_item_id
|
|
16
|
+
type:
|
|
17
|
+
required: false
|
|
18
|
+
value_ref: nv_item_type
|
|
19
|
+
by:
|
|
20
|
+
required: false
|
|
21
|
+
value_ref: nv_item_by
|
|
22
|
+
time:
|
|
23
|
+
required: false
|
|
24
|
+
value_ref: nv_item_time
|
|
25
|
+
title:
|
|
26
|
+
required: false
|
|
27
|
+
value_ref: nv_item_title
|
|
28
|
+
text:
|
|
29
|
+
required: false
|
|
30
|
+
value_ref: nv_item_text
|
|
31
|
+
url:
|
|
32
|
+
required: false
|
|
33
|
+
value_ref: nv_item_url
|
|
34
|
+
score:
|
|
35
|
+
required: false
|
|
36
|
+
value_ref: nv_item_score
|
|
37
|
+
descendants:
|
|
38
|
+
required: false
|
|
39
|
+
value_ref: nv_item_descendants
|
|
40
|
+
parent:
|
|
41
|
+
required: false
|
|
42
|
+
value_ref: nv_item_parent
|
|
43
|
+
poll:
|
|
44
|
+
required: false
|
|
45
|
+
value_ref: nv_item_poll
|
|
46
|
+
parts:
|
|
47
|
+
required: false
|
|
48
|
+
value_ref: nv_item_parts
|
|
49
|
+
deleted:
|
|
50
|
+
required: false
|
|
51
|
+
value_ref: nv_item_deleted
|
|
52
|
+
dead:
|
|
53
|
+
required: false
|
|
54
|
+
value_ref: nv_item_dead
|
|
55
|
+
relations:
|
|
56
|
+
kids:
|
|
57
|
+
description: Direct child item ids (typically comments under a story).
|
|
58
|
+
target: Item
|
|
59
|
+
cardinality: many
|
|
60
|
+
materialize:
|
|
61
|
+
kind: from_parent_get
|
|
62
|
+
path:
|
|
63
|
+
- key: kids
|
|
64
|
+
- wildcard: true
|
|
65
|
+
poll_options:
|
|
66
|
+
description: Poll option rows referenced by parts (each id is an Item).
|
|
67
|
+
target: Item
|
|
68
|
+
cardinality: many
|
|
69
|
+
materialize:
|
|
70
|
+
kind: from_parent_get
|
|
71
|
+
path:
|
|
72
|
+
- key: parts
|
|
73
|
+
- wildcard: true
|
|
74
|
+
User:
|
|
75
|
+
id_field: id
|
|
76
|
+
description: A Hacker News user profile keyed by username. The submitted list holds item ids only; fetch each via item_get.
|
|
77
|
+
when you need titles or comment bodies.
|
|
78
|
+
fields:
|
|
79
|
+
id:
|
|
80
|
+
required: true
|
|
81
|
+
value_ref: nv_user_id
|
|
82
|
+
created:
|
|
83
|
+
required: false
|
|
84
|
+
value_ref: nv_user_created
|
|
85
|
+
karma:
|
|
86
|
+
required: false
|
|
87
|
+
value_ref: nv_user_karma
|
|
88
|
+
about:
|
|
89
|
+
required: false
|
|
90
|
+
value_ref: nv_user_about
|
|
91
|
+
delay:
|
|
92
|
+
required: false
|
|
93
|
+
value_ref: nv_user_delay
|
|
94
|
+
relations:
|
|
95
|
+
submitted:
|
|
96
|
+
description: Item ids this user has submitted.
|
|
97
|
+
target: Item
|
|
98
|
+
cardinality: many
|
|
99
|
+
materialize:
|
|
100
|
+
kind: from_parent_get
|
|
101
|
+
path:
|
|
102
|
+
- key: submitted
|
|
103
|
+
- wildcard: true
|
|
104
|
+
MaxItemId:
|
|
105
|
+
id_field: id
|
|
106
|
+
description: Current largest item id reported by maxitem.json (a bare JSON integer on the wire). Hydrate the referenced.
|
|
107
|
+
row with item_get when you need the full record.
|
|
108
|
+
fields:
|
|
109
|
+
id:
|
|
110
|
+
required: true
|
|
111
|
+
value_ref: nv_max_item_id_id
|
|
112
|
+
RecentUpdatedItem:
|
|
113
|
+
id_field: id
|
|
114
|
+
description: Item ids listed under updates.json items (live slice of ids with recent activity). Each entry is an id only.
|
|
115
|
+
use item_get for payloads.
|
|
116
|
+
fields:
|
|
117
|
+
id:
|
|
118
|
+
required: true
|
|
119
|
+
value_ref: nv_recent_updated_item_id
|
|
120
|
+
RecentUpdatedUser:
|
|
121
|
+
id_field: id
|
|
122
|
+
description: Usernames listed under updates.json profiles (accounts whose profile data changed). Each entry is the username.
|
|
123
|
+
string only; use user_get for karma and about text.
|
|
124
|
+
fields:
|
|
125
|
+
id:
|
|
126
|
+
required: true
|
|
127
|
+
value_ref: nv_recent_updated_user_id
|
|
128
|
+
capabilities:
|
|
129
|
+
item_search:
|
|
130
|
+
description: Full-text search over HN via the public Algolia index (https://hn.algolia.com/api). Returns story Item rows.
|
|
131
|
+
with id from objectID and title/url from the hit; use item_get to hydrate the full Firebase record, kids, and score.
|
|
132
|
+
kind: search
|
|
133
|
+
entity: Item
|
|
134
|
+
provides:
|
|
135
|
+
- id
|
|
136
|
+
- title
|
|
137
|
+
- url
|
|
138
|
+
parameters:
|
|
139
|
+
- name: query
|
|
140
|
+
value_ref: nv_wire_str_short
|
|
141
|
+
required: true
|
|
142
|
+
role: search
|
|
143
|
+
- name: tags
|
|
144
|
+
value_ref: nv_wire_str_short
|
|
145
|
+
required: false
|
|
146
|
+
role: filter
|
|
147
|
+
- name: page
|
|
148
|
+
value_ref: nv_wire_int
|
|
149
|
+
required: false
|
|
150
|
+
role: response_control
|
|
151
|
+
- name: per_page
|
|
152
|
+
value_ref: nv_wire_int
|
|
153
|
+
required: false
|
|
154
|
+
role: response_control
|
|
155
|
+
item_search_by_date:
|
|
156
|
+
description: Same as item_search but sorted by time (search_by_date). Use for recency; combine with item_get for full.
|
|
157
|
+
items.
|
|
158
|
+
kind: search
|
|
159
|
+
entity: Item
|
|
160
|
+
provides:
|
|
161
|
+
- id
|
|
162
|
+
- title
|
|
163
|
+
- url
|
|
164
|
+
parameters:
|
|
165
|
+
- name: query
|
|
166
|
+
value_ref: nv_wire_str_short
|
|
167
|
+
required: true
|
|
168
|
+
role: search
|
|
169
|
+
- name: tags
|
|
170
|
+
value_ref: nv_wire_str_short
|
|
171
|
+
required: false
|
|
172
|
+
role: filter
|
|
173
|
+
- name: page
|
|
174
|
+
value_ref: nv_wire_int
|
|
175
|
+
required: false
|
|
176
|
+
role: response_control
|
|
177
|
+
- name: per_page
|
|
178
|
+
value_ref: nv_wire_int
|
|
179
|
+
required: false
|
|
180
|
+
role: response_control
|
|
181
|
+
item_feed_query:
|
|
182
|
+
description: Ordered list of item ids for a public feed (top, new, best, ask, show, or job). Each entry is an Item id only.
|
|
183
|
+
hydrate with item_get for full records and kids.
|
|
184
|
+
kind: query
|
|
185
|
+
entity: Item
|
|
186
|
+
provides:
|
|
187
|
+
- id
|
|
188
|
+
parameters:
|
|
189
|
+
- name: feed
|
|
190
|
+
value_ref: nv_item_feed_query_feed
|
|
191
|
+
required: true
|
|
192
|
+
role: filter
|
|
193
|
+
max_item_id_query:
|
|
194
|
+
description: Current largest item id (maxitem.json).
|
|
195
|
+
kind: query
|
|
196
|
+
entity: MaxItemId
|
|
197
|
+
provides:
|
|
198
|
+
- id
|
|
199
|
+
recent_updated_item_query:
|
|
200
|
+
description: Item ids from the live updates snapshot (updates.json items array).
|
|
201
|
+
kind: query
|
|
202
|
+
entity: RecentUpdatedItem
|
|
203
|
+
provides:
|
|
204
|
+
- id
|
|
205
|
+
recent_updated_user_query:
|
|
206
|
+
description: Usernames from the live updates snapshot (updates.json profiles array).
|
|
207
|
+
kind: query
|
|
208
|
+
entity: RecentUpdatedUser
|
|
209
|
+
provides:
|
|
210
|
+
- id
|
|
211
|
+
item_get:
|
|
212
|
+
kind: get
|
|
213
|
+
entity: Item
|
|
214
|
+
provides:
|
|
215
|
+
- id
|
|
216
|
+
- type
|
|
217
|
+
- by
|
|
218
|
+
- time
|
|
219
|
+
- title
|
|
220
|
+
- text
|
|
221
|
+
- url
|
|
222
|
+
- score
|
|
223
|
+
- descendants
|
|
224
|
+
- parent
|
|
225
|
+
- poll
|
|
226
|
+
- parts
|
|
227
|
+
- deleted
|
|
228
|
+
- dead
|
|
229
|
+
user_get:
|
|
230
|
+
description: Load a user profile by username.
|
|
231
|
+
kind: get
|
|
232
|
+
entity: User
|
|
233
|
+
provides:
|
|
234
|
+
- id
|
|
235
|
+
- created
|
|
236
|
+
- karma
|
|
237
|
+
- about
|
|
238
|
+
- delay
|
|
239
|
+
values:
|
|
240
|
+
nv_item_by:
|
|
241
|
+
type: string
|
|
242
|
+
string_semantics: short
|
|
243
|
+
description: Username of the author when present.
|
|
244
|
+
nv_item_dead:
|
|
245
|
+
type: boolean
|
|
246
|
+
description: Whether the item is dead.
|
|
247
|
+
nv_item_deleted:
|
|
248
|
+
type: boolean
|
|
249
|
+
description: Whether the item was deleted.
|
|
250
|
+
nv_item_descendants:
|
|
251
|
+
type: integer
|
|
252
|
+
description: Comment count for stories when known.
|
|
253
|
+
nv_item_feed_query_feed:
|
|
254
|
+
type: select
|
|
255
|
+
allowed_values:
|
|
256
|
+
- top
|
|
257
|
+
- new
|
|
258
|
+
- best
|
|
259
|
+
- ask
|
|
260
|
+
- show
|
|
261
|
+
- job
|
|
262
|
+
nv_item_id:
|
|
263
|
+
type: integer
|
|
264
|
+
description: Numeric item id used in Firebase paths.
|
|
265
|
+
nv_item_parent:
|
|
266
|
+
type: integer
|
|
267
|
+
description: Parent item id for comments and poll options.
|
|
268
|
+
nv_item_poll:
|
|
269
|
+
type: integer
|
|
270
|
+
description: Poll id when this item is a story that hosts a poll.
|
|
271
|
+
nv_item_score:
|
|
272
|
+
type: integer
|
|
273
|
+
description: Net votes for stories and jobs when present.
|
|
274
|
+
nv_item_text:
|
|
275
|
+
type: string
|
|
276
|
+
string_semantics: markdown
|
|
277
|
+
description: Comment or story text payload when present.
|
|
278
|
+
nv_item_time:
|
|
279
|
+
type: integer
|
|
280
|
+
description: Unix time the item was created.
|
|
281
|
+
nv_item_title:
|
|
282
|
+
type: string
|
|
283
|
+
string_semantics: short
|
|
284
|
+
description: Story or job title when applicable.
|
|
285
|
+
nv_item_type:
|
|
286
|
+
type: string
|
|
287
|
+
string_semantics: short
|
|
288
|
+
description: Wire kind such as story, comment, job, poll, or pollopt.
|
|
289
|
+
nv_item_url:
|
|
290
|
+
type: string
|
|
291
|
+
string_semantics: short
|
|
292
|
+
description: Outbound link for link posts when present.
|
|
293
|
+
nv_max_item_id_id:
|
|
294
|
+
type: integer
|
|
295
|
+
description: Largest item id currently allocated on HN.
|
|
296
|
+
nv_recent_updated_item_id:
|
|
297
|
+
type: integer
|
|
298
|
+
description: Item id from the live updates snapshot.
|
|
299
|
+
nv_recent_updated_user_id:
|
|
300
|
+
type: string
|
|
301
|
+
string_semantics: short
|
|
302
|
+
description: Username from the live updates snapshot.
|
|
303
|
+
nv_user_about:
|
|
304
|
+
type: string
|
|
305
|
+
string_semantics: markdown
|
|
306
|
+
description: Self-description text when set.
|
|
307
|
+
nv_user_created:
|
|
308
|
+
type: integer
|
|
309
|
+
description: Unix time the account was created.
|
|
310
|
+
nv_user_delay:
|
|
311
|
+
type: integer
|
|
312
|
+
description: Account delay setting when present.
|
|
313
|
+
nv_user_id:
|
|
314
|
+
type: string
|
|
315
|
+
string_semantics: short
|
|
316
|
+
description: Unique username.
|
|
317
|
+
nv_user_karma:
|
|
318
|
+
type: integer
|
|
319
|
+
description: User karma score.
|
|
320
|
+
nv_wire_int:
|
|
321
|
+
type: integer
|
|
322
|
+
nv_wire_str_short:
|
|
323
|
+
type: string
|
|
324
|
+
string_semantics: short
|
|
325
|
+
nv_item_parts:
|
|
326
|
+
type: array
|
|
327
|
+
items:
|
|
328
|
+
value_ref: nv_wire_int
|
|
329
|
+
description: Poll option item ids when type is poll (empty or absent otherwise).
|