@clien-ai/mcp 0.1.2 → 0.1.4
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 +19 -0
- package/dist/server.js +29 -73
- package/dist/server.js.map +1 -1
- package/dist/tools/errors.js +23 -0
- package/dist/tools/errors.js.map +1 -0
- package/dist/tools/interview.js +263 -0
- package/dist/tools/interview.js.map +1 -0
- package/dist/tools/personas.js +192 -0
- package/dist/tools/personas.js.map +1 -0
- package/dist/tools/projects.js +195 -0
- package/dist/tools/projects.js.map +1 -0
- package/dist/tools/registry.js +147 -0
- package/dist/tools/registry.js.map +1 -0
- package/dist/tools/reports.js +210 -0
- package/dist/tools/reports.js.map +1 -0
- package/dist/tools/research.js +59 -17
- package/dist/tools/research.js.map +1 -1
- package/dist/tools/schema.js +25 -0
- package/dist/tools/schema.js.map +1 -0
- package/dist/tools/status.js +334 -0
- package/dist/tools/status.js.map +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -68,6 +68,25 @@ the top 3 threats", etc.
|
|
|
68
68
|
|
|
69
69
|
---
|
|
70
70
|
|
|
71
|
+
## Recipes: composing beyond the hero
|
|
72
|
+
|
|
73
|
+
`clien_research` is the hero — the recommended default for validating an idea
|
|
74
|
+
end to end. Alongside it, the server exposes granular primitives (`create_persona`,
|
|
75
|
+
`interview_persona`, `list_projects` / `create_project`, `list_reports` /
|
|
76
|
+
`get_report`, `clien_research_status`) you can sequence for targeted, cheaper
|
|
77
|
+
checks.
|
|
78
|
+
|
|
79
|
+
See [`docs/recipes/`](../docs/recipes/) for the supported compositions:
|
|
80
|
+
|
|
81
|
+
- [Competitor spot-check](../docs/recipes/competitor-spot-check.md) — read `competitors[]` from a report instead of commissioning a new study
|
|
82
|
+
- [Pivot viability](../docs/recipes/pivot-viability.md) — research a pivot and compare it against the original
|
|
83
|
+
- [New-market persona validation](../docs/recipes/new-market-persona-validation.md) — `create_persona` → `interview_persona`, no credits, no full run
|
|
84
|
+
|
|
85
|
+
Rule of thumb: for a **full** validation, call the hero; reach for a primitive
|
|
86
|
+
composition only for a **narrow, cheaper probe**.
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
71
90
|
## Inputs
|
|
72
91
|
|
|
73
92
|
| Field | Type | Required | Default | Notes |
|
package/dist/server.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MCP stdio server entry point.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* The tool surface lives in `tools/registry.ts` — both `tools/list` and
|
|
5
|
+
* `tools/call` iterate the `TOOLS` array, so adding a tool needs no edits here.
|
|
6
|
+
* Auth happens lazily on first tool call so the server can announce its tool
|
|
7
|
+
* list without a browser interruption.
|
|
6
8
|
*/
|
|
7
9
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
8
10
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
@@ -10,7 +12,8 @@ import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextpro
|
|
|
10
12
|
import { loadConfig, PACKAGE_NAME, PACKAGE_VERSION } from './config.js';
|
|
11
13
|
import { ensureSession, } from './auth/oauth.js';
|
|
12
14
|
import { clearTokens, loadRefreshToken, saveRefreshToken, withTokenLock, } from './auth/storage.js';
|
|
13
|
-
import {
|
|
15
|
+
import { ToolError } from './tools/errors.js';
|
|
16
|
+
import { TOOLS } from './tools/registry.js';
|
|
14
17
|
function log(...args) {
|
|
15
18
|
// stderr only — stdout is the MCP transport.
|
|
16
19
|
console.error('[clien-mcp]', ...args);
|
|
@@ -28,62 +31,6 @@ async function defaultBrowserOpener() {
|
|
|
28
31
|
await fn(url);
|
|
29
32
|
};
|
|
30
33
|
}
|
|
31
|
-
/**
|
|
32
|
-
* Input schema for the tool advert. Hand-rolled JSON Schema so we avoid the
|
|
33
|
-
* `zod-to-json-schema` dependency. Must stay in sync with `ResearchInputSchema`
|
|
34
|
-
* in `src/tools/research.ts` — the Zod parser is still the source of truth at
|
|
35
|
-
* runtime; this is only the spec advertised to the caller.
|
|
36
|
-
*
|
|
37
|
-
* Drift surfaces silently: the Zod parser still accepts the input, but strict
|
|
38
|
-
* MCP clients (`additionalProperties: false`) strip undeclared fields before
|
|
39
|
-
* they reach the parser, and LLM callers never learn the field exists.
|
|
40
|
-
* Enforcement: `__tests__/schema-contract.test.ts` walks both schemas and
|
|
41
|
-
* fails if their property keys or required-field sets diverge. Exported here
|
|
42
|
-
* so that test can import it. See
|
|
43
|
-
* `docs/solutions/design-patterns/cross-package-schema-duplication-contract-2026-05-12.md`.
|
|
44
|
-
*/
|
|
45
|
-
export const RESEARCH_INPUT_SCHEMA = {
|
|
46
|
-
type: 'object',
|
|
47
|
-
properties: {
|
|
48
|
-
idea: {
|
|
49
|
-
type: 'string',
|
|
50
|
-
minLength: 1,
|
|
51
|
-
maxLength: 10_000,
|
|
52
|
-
description: 'The product idea, feature hypothesis, or research question to validate. ' +
|
|
53
|
-
'Can be a one-liner or several paragraphs — more context yields a better report.',
|
|
54
|
-
},
|
|
55
|
-
depth: {
|
|
56
|
-
type: 'string',
|
|
57
|
-
enum: ['quick', 'deep'],
|
|
58
|
-
default: 'deep',
|
|
59
|
-
description: '"deep" (default) runs a 15-25 minute full research pass. ' +
|
|
60
|
-
'"quick" caps cost and runs a tighter pass.',
|
|
61
|
-
},
|
|
62
|
-
project_id: {
|
|
63
|
-
type: 'string',
|
|
64
|
-
format: 'uuid',
|
|
65
|
-
description: 'Optional Clien.ai project UUID to attach this run to. When omitted, ' +
|
|
66
|
-
'the run is standalone and not associated with any project.',
|
|
67
|
-
},
|
|
68
|
-
project_name: {
|
|
69
|
-
type: 'string',
|
|
70
|
-
minLength: 1,
|
|
71
|
-
maxLength: 100,
|
|
72
|
-
description: 'Name for a new Clien.ai project to attach this run to. You SHOULD ' +
|
|
73
|
-
'supply this whenever `project_id` is not set, otherwise the ' +
|
|
74
|
-
'resulting report will not be discoverable in the Clien.ai web UI. ' +
|
|
75
|
-
'Derive a short (2-4 word) human-readable name from the idea — ' +
|
|
76
|
-
'typically the product name. Guidance on when to create a new ' +
|
|
77
|
-
'project vs. reuse an existing one: create a NEW project (supply a ' +
|
|
78
|
-
'fresh `project_name`) for each distinct research idea or product ' +
|
|
79
|
-
'concept; REUSE an existing `project_id` only when the user ' +
|
|
80
|
-
'explicitly asks to add follow-up research to a prior run on the ' +
|
|
81
|
-
'same idea. Ignored when `project_id` is also provided.',
|
|
82
|
-
},
|
|
83
|
-
},
|
|
84
|
-
required: ['idea'],
|
|
85
|
-
additionalProperties: false,
|
|
86
|
-
};
|
|
87
34
|
export function createMcpServer() {
|
|
88
35
|
const config = loadConfig();
|
|
89
36
|
// Per-instance session cache. Scoping this to the closure (rather than the
|
|
@@ -115,20 +62,23 @@ export function createMcpServer() {
|
|
|
115
62
|
}
|
|
116
63
|
const server = new Server({ name: PACKAGE_NAME, version: PACKAGE_VERSION }, { capabilities: { tools: {} } });
|
|
117
64
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
118
|
-
tools:
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
'15-25 minutes; progress is streamed via notifications/progress. ' +
|
|
124
|
-
'First call opens a browser for one-time OAuth login; subsequent calls reuse the stored refresh token.',
|
|
125
|
-
inputSchema: RESEARCH_INPUT_SCHEMA,
|
|
126
|
-
},
|
|
127
|
-
],
|
|
65
|
+
tools: TOOLS.map((tool) => ({
|
|
66
|
+
name: tool.name,
|
|
67
|
+
description: tool.description,
|
|
68
|
+
inputSchema: tool.jsonSchema,
|
|
69
|
+
})),
|
|
128
70
|
}));
|
|
129
71
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
130
|
-
|
|
131
|
-
|
|
72
|
+
const toolName = request.params.name;
|
|
73
|
+
// Unknown-tool check happens BEFORE the try/catch so the MCP SDK
|
|
74
|
+
// converts a thrown Error into a JSON-RPC protocol error (per spec:
|
|
75
|
+
// unknown tool name is a protocol-level fault). Putting the check
|
|
76
|
+
// inside the try-catch would convert it to a tool-level `isError`
|
|
77
|
+
// response — the wrong shape, and a regression caught by
|
|
78
|
+
// server.test.ts > 'unknown tool name surfaces an error'.
|
|
79
|
+
const tool = TOOLS.find((t) => t.name === toolName);
|
|
80
|
+
if (!tool) {
|
|
81
|
+
throw new Error(`Unknown tool: ${toolName}`);
|
|
132
82
|
}
|
|
133
83
|
// Per MCP spec, `progressToken` may be a string OR a number, and `0` or
|
|
134
84
|
// `''` are valid token values. A truthy check (`progressToken ?`) silently
|
|
@@ -154,7 +104,7 @@ export function createMcpServer() {
|
|
|
154
104
|
: undefined;
|
|
155
105
|
try {
|
|
156
106
|
const session = await getSession();
|
|
157
|
-
const result = await
|
|
107
|
+
const result = await tool.handler(request.params.arguments, {
|
|
158
108
|
config,
|
|
159
109
|
session,
|
|
160
110
|
onProgress,
|
|
@@ -166,15 +116,21 @@ export function createMcpServer() {
|
|
|
166
116
|
};
|
|
167
117
|
}
|
|
168
118
|
catch (err) {
|
|
169
|
-
const message = err instanceof
|
|
119
|
+
const message = err instanceof ToolError
|
|
170
120
|
? err.userMessage
|
|
171
121
|
: err instanceof Error
|
|
172
122
|
? err.message
|
|
173
123
|
: String(err);
|
|
124
|
+
// Preserve the job_id on error responses so the host AI can pivot to
|
|
125
|
+
// `clien_research_status` (or surface the id to the user) without
|
|
126
|
+
// having to parse it out of the human-readable error text. Tool error
|
|
127
|
+
// classes carry a `jobId` field when one is known.
|
|
128
|
+
const errorJobId = err instanceof ToolError ? err.jobId : undefined;
|
|
174
129
|
log('tool error:', message);
|
|
175
130
|
return {
|
|
176
131
|
isError: true,
|
|
177
132
|
content: [{ type: 'text', text: message }],
|
|
133
|
+
...(errorJobId !== undefined ? { _meta: { job_id: errorJobId } } : {}),
|
|
178
134
|
};
|
|
179
135
|
}
|
|
180
136
|
});
|
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared base class for tool-level errors.
|
|
3
|
+
*
|
|
4
|
+
* Every registry tool signals a user-recoverable failure by throwing a
|
|
5
|
+
* subclass of `ToolError`. The server's `CallTool` catch block maps any
|
|
6
|
+
* `ToolError` to a tool-level `{ isError: true, content, _meta.job_id? }`
|
|
7
|
+
* response (as opposed to a JSON-RPC protocol error). Carrying `userMessage`
|
|
8
|
+
* and the optional `jobId` on a single base means the dispatch needs exactly
|
|
9
|
+
* one `instanceof ToolError` check regardless of how many tools the registry
|
|
10
|
+
* holds — see `src/server.ts`.
|
|
11
|
+
*/
|
|
12
|
+
export class ToolError extends Error {
|
|
13
|
+
userMessage;
|
|
14
|
+
jobId;
|
|
15
|
+
constructor(userMessage, opts = {}) {
|
|
16
|
+
super(userMessage);
|
|
17
|
+
this.userMessage = userMessage;
|
|
18
|
+
this.jobId = opts.jobId;
|
|
19
|
+
if (opts.cause !== undefined)
|
|
20
|
+
this.cause = opts.cause;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/tools/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,MAAM,OAAO,SAAU,SAAQ,KAAK;IACzB,WAAW,CAAQ;IACnB,KAAK,CAAS;IACvB,YAAY,WAAmB,EAAE,OAA4C,EAAE;QAC7E,KAAK,CAAC,WAAW,CAAC,CAAA;QAClB,IAAI,CAAC,WAAW,GAAG,WAAW,CAAA;QAC9B,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAA;QACvB,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS;YAAG,IAA4B,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAA;IAChF,CAAC;CACF"}
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `interview_persona` — stateful, full-lifecycle interview tool over the
|
|
3
|
+
* interview REST API (FUL-17 lifecycle + FUL-43 conversational turn). Lets an
|
|
4
|
+
* agent drive a real multi-turn interview with a persona: start it, exchange
|
|
5
|
+
* turns (each returns the persona's FULL reply), read the transcript, and
|
|
6
|
+
* complete/reopen — reusing the same lifecycle as the web app (greeting seed,
|
|
7
|
+
* auto-summary on message thresholds, message-count maintenance), so an
|
|
8
|
+
* MCP-driven interview is indistinguishable from a web one in the DB.
|
|
9
|
+
*
|
|
10
|
+
* Like `personas.ts` / `projects.ts` this is a thin bearer-auth HTTP client: it
|
|
11
|
+
* reuses the shared `apiCall` from `research.ts` (401→refresh→retry, per-endpoint
|
|
12
|
+
* timeouts). No new HTTP machinery.
|
|
13
|
+
*
|
|
14
|
+
* One tool, many actions (an `action` discriminator on a FLAT object schema —
|
|
15
|
+
* the registry type and the schema-contract test both assume a `z.ZodObject`, so
|
|
16
|
+
* a `discriminatedUnion` is off the table). Per-action required fields are
|
|
17
|
+
* enforced in the handler, not via `.superRefine` (which would yield a
|
|
18
|
+
* `ZodEffects`, not a `ZodObject`).
|
|
19
|
+
*
|
|
20
|
+
* Contract (all bearer-authenticated, ownership derived from the parent project):
|
|
21
|
+
* - start → POST /api/interviews { projectId, personaId, withFirstMessage:true, ephemeral } → { interview, personaName, firstMessageContent }
|
|
22
|
+
* - turn → POST /api/interviews/{id}/turn { content } → { reply, message, userMessage, messageCount }
|
|
23
|
+
* - transcript → GET /api/interviews/{id} → { interview }
|
|
24
|
+
* - complete → PATCH /api/interviews/{id} { status:'completed' } → { interview }
|
|
25
|
+
* - reopen → PATCH /api/interviews/{id} { status:'active' } → { interview }
|
|
26
|
+
*
|
|
27
|
+
* The backend stamps `source='mcp'` automatically for bearer callers (FUL-21);
|
|
28
|
+
* `ephemeral: true` on `start` sets `include_in_insights=false`.
|
|
29
|
+
*
|
|
30
|
+
* Scope note: a `turn` generates a real AI reply, metered today only by the
|
|
31
|
+
* monthly usage limit. Per-turn credit debiting is deferred to FUL-33 (the turn
|
|
32
|
+
* endpoint and the generalized per-tool spend RPC / cost table aren't built yet).
|
|
33
|
+
*/
|
|
34
|
+
import { z } from 'zod';
|
|
35
|
+
import { ToolError } from './errors.js';
|
|
36
|
+
import { apiCall, categorizeFetchError, truncate, } from './research.js';
|
|
37
|
+
const INTERVIEW_FETCH_TIMEOUT_MS = 60_000;
|
|
38
|
+
/** Upper bound on a turn's question — matches the server's `MESSAGE_CONTENT_MAX`. */
|
|
39
|
+
const CONTENT_MAX = 10_000;
|
|
40
|
+
export const InterviewPersonaInputSchema = z.object({
|
|
41
|
+
action: z
|
|
42
|
+
.enum(['start', 'turn', 'transcript', 'complete', 'reopen'])
|
|
43
|
+
.describe('What to do this call. Drive a full interview as a loop: `start` once to open an ' +
|
|
44
|
+
'interview (seeds the persona\'s greeting), then `turn` repeatedly — send a question, ' +
|
|
45
|
+
'read the persona\'s full reply, decide the next question. `transcript` re-reads the ' +
|
|
46
|
+
'whole conversation. `complete` closes the interview when you\'ve learned enough ' +
|
|
47
|
+
'(5-12 turns is typical); `reopen` resumes a completed one.'),
|
|
48
|
+
projectId: z
|
|
49
|
+
.string()
|
|
50
|
+
.uuid()
|
|
51
|
+
.optional()
|
|
52
|
+
.describe('action=start: UUID of the project the interview belongs to. Get it from `list_projects` ' +
|
|
53
|
+
'or `create_project`. You are 404\'d if you do not own it.'),
|
|
54
|
+
personaId: z
|
|
55
|
+
.string()
|
|
56
|
+
.uuid()
|
|
57
|
+
.optional()
|
|
58
|
+
.describe('action=start: UUID of the persona to interview (must belong to `projectId`). Get it from ' +
|
|
59
|
+
'`create_persona` (`_meta.persona.id`).'),
|
|
60
|
+
interviewId: z
|
|
61
|
+
.string()
|
|
62
|
+
.uuid()
|
|
63
|
+
.optional()
|
|
64
|
+
.describe('action=turn/transcript/complete/reopen: UUID of the interview to act on. Returned by ' +
|
|
65
|
+
'`start` in `_meta.interview.id`.'),
|
|
66
|
+
content: z
|
|
67
|
+
.string()
|
|
68
|
+
.trim()
|
|
69
|
+
.min(1, 'content must be non-empty')
|
|
70
|
+
.max(CONTENT_MAX, `content too long (max ${CONTENT_MAX} chars)`)
|
|
71
|
+
.optional()
|
|
72
|
+
.describe('action=turn: the next question to ask the persona. The persona\'s full reply comes back ' +
|
|
73
|
+
'in the tool result text (and `_meta.reply`).'),
|
|
74
|
+
ephemeral: z
|
|
75
|
+
.boolean()
|
|
76
|
+
.optional()
|
|
77
|
+
.default(false)
|
|
78
|
+
.describe('action=start: set true for a scratch/throwaway interview that should NOT count toward the ' +
|
|
79
|
+
'project\'s aggregate insights (sets include_in_insights=false). Leave false (the default) ' +
|
|
80
|
+
'for a real interview you want reflected in project-level analysis.'),
|
|
81
|
+
});
|
|
82
|
+
export class InterviewToolError extends ToolError {
|
|
83
|
+
}
|
|
84
|
+
function formatZodError(error) {
|
|
85
|
+
return error.issues.map((i) => `${i.path.join('.') || 'input'}: ${i.message}`).join('; ');
|
|
86
|
+
}
|
|
87
|
+
function makeCtx(deps) {
|
|
88
|
+
return { config: deps.config, session: deps.session, fetch: deps.fetch ?? fetch };
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Shared non-ok handling, mirroring `personas.ts`: 401 drops the cached session
|
|
92
|
+
* (so the next tool call rebuilds from disk) and surfaces a re-auth message;
|
|
93
|
+
* 404 means the interview/project isn't owned by the caller; 400 surfaces the
|
|
94
|
+
* server's validation detail; everything else surfaces a generic HTTP error.
|
|
95
|
+
*/
|
|
96
|
+
async function throwForResponse(response, deps, action) {
|
|
97
|
+
const category = categorizeFetchError(response.status);
|
|
98
|
+
if (category === 'auth') {
|
|
99
|
+
if (response.refreshError?.kind !== 'transient')
|
|
100
|
+
deps.invalidateSession?.();
|
|
101
|
+
throw new InterviewToolError('Session expired. Re-run any Clien.ai tool to re-authenticate, then try again.');
|
|
102
|
+
}
|
|
103
|
+
if (response.status === 404) {
|
|
104
|
+
throw new InterviewToolError(`Could not ${action} — interview or project not found, or you don't own it. ` +
|
|
105
|
+
'Confirm the ids (list_projects / the id from `start`).');
|
|
106
|
+
}
|
|
107
|
+
const text = await response.text().catch(() => '');
|
|
108
|
+
let detail = truncate(text, 200);
|
|
109
|
+
try {
|
|
110
|
+
const parsed = JSON.parse(text);
|
|
111
|
+
if (parsed?.error)
|
|
112
|
+
detail = `${parsed.error}${parsed.code ? ` (${parsed.code})` : ''}`;
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
// non-JSON body — fall back to the truncated raw text
|
|
116
|
+
}
|
|
117
|
+
if (response.status === 400) {
|
|
118
|
+
throw new InterviewToolError(`Could not ${action} — ${detail}`);
|
|
119
|
+
}
|
|
120
|
+
throw new InterviewToolError(`Failed to ${action} (HTTP ${response.status}): ${detail}`);
|
|
121
|
+
}
|
|
122
|
+
/** Run one `apiCall`, converting timeout/abort into a clear tool error. */
|
|
123
|
+
async function call(ctx, method, path, action, body) {
|
|
124
|
+
try {
|
|
125
|
+
return await apiCall(ctx, method, path, {
|
|
126
|
+
...(body ? { body } : {}),
|
|
127
|
+
timeoutMs: INTERVIEW_FETCH_TIMEOUT_MS,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
catch (err) {
|
|
131
|
+
const e = err;
|
|
132
|
+
if (e.name === 'AbortError' || e.name === 'TimeoutError') {
|
|
133
|
+
throw new InterviewToolError(`Request to ${action} timed out after ${INTERVIEW_FETCH_TIMEOUT_MS / 1000}s. ` +
|
|
134
|
+
'It may or may not have been applied — fetch the transcript before retrying.');
|
|
135
|
+
}
|
|
136
|
+
throw err;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
async function readJson(response) {
|
|
140
|
+
return (await response.json().catch(() => null));
|
|
141
|
+
}
|
|
142
|
+
function formatTranscript(interview) {
|
|
143
|
+
const messages = interview.messages ?? [];
|
|
144
|
+
const lines = messages.map((m) => {
|
|
145
|
+
const speaker = m.role === 'assistant' ? 'Persona' : 'Interviewer';
|
|
146
|
+
return `${speaker}: ${m.content ?? ''}`;
|
|
147
|
+
});
|
|
148
|
+
const header = `Interview ${interview.id ?? '?'} — status: ${interview.status ?? 'unknown'}, ` +
|
|
149
|
+
`${messages.length} message${messages.length === 1 ? '' : 's'}.`;
|
|
150
|
+
return lines.length > 0 ? `${header}\n\n${lines.join('\n\n')}` : header;
|
|
151
|
+
}
|
|
152
|
+
async function startInterview(input, ctx, deps) {
|
|
153
|
+
if (!input.projectId || !input.personaId) {
|
|
154
|
+
throw new InterviewToolError("action 'start' requires projectId and personaId.");
|
|
155
|
+
}
|
|
156
|
+
const response = await call(ctx, 'POST', '/api/interviews', 'start interview', {
|
|
157
|
+
projectId: input.projectId,
|
|
158
|
+
personaId: input.personaId,
|
|
159
|
+
withFirstMessage: true,
|
|
160
|
+
ephemeral: input.ephemeral,
|
|
161
|
+
});
|
|
162
|
+
if (!response.ok)
|
|
163
|
+
await throwForResponse(response, deps, 'start interview');
|
|
164
|
+
const json = await readJson(response);
|
|
165
|
+
const interview = json?.interview;
|
|
166
|
+
if (!interview?.id) {
|
|
167
|
+
throw new InterviewToolError('Interview creation returned no interview id; the operation may not have succeeded.');
|
|
168
|
+
}
|
|
169
|
+
const who = json?.personaName ? ` with ${json.personaName}` : '';
|
|
170
|
+
const greeting = json?.firstMessageContent ? `\nGreeting: ${json.firstMessageContent}` : '';
|
|
171
|
+
const insightsNote = input.ephemeral
|
|
172
|
+
? ' Marked ephemeral — excluded from project insights.'
|
|
173
|
+
: '';
|
|
174
|
+
return {
|
|
175
|
+
content: [
|
|
176
|
+
{
|
|
177
|
+
type: 'text',
|
|
178
|
+
text: `Started interview ${interview.id}${who}.${insightsNote}${greeting}\n` +
|
|
179
|
+
"Ask questions with action='turn'; close it with action='complete' when done.",
|
|
180
|
+
},
|
|
181
|
+
],
|
|
182
|
+
_meta: { interview, personaName: json?.personaName, firstMessageContent: json?.firstMessageContent },
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
async function sendTurn(input, ctx, deps) {
|
|
186
|
+
if (!input.interviewId || !input.content) {
|
|
187
|
+
throw new InterviewToolError("action 'turn' requires interviewId and content.");
|
|
188
|
+
}
|
|
189
|
+
const response = await call(ctx, 'POST', `/api/interviews/${input.interviewId}/turn`, 'send interview turn', { content: input.content });
|
|
190
|
+
if (!response.ok)
|
|
191
|
+
await throwForResponse(response, deps, 'send interview turn');
|
|
192
|
+
const json = await readJson(response);
|
|
193
|
+
const reply = json?.reply;
|
|
194
|
+
if (typeof reply !== 'string' || reply.length === 0) {
|
|
195
|
+
throw new InterviewToolError('Turn returned no persona reply; the turn may not have succeeded.');
|
|
196
|
+
}
|
|
197
|
+
return {
|
|
198
|
+
content: [{ type: 'text', text: reply }],
|
|
199
|
+
_meta: {
|
|
200
|
+
reply,
|
|
201
|
+
messageCount: json?.messageCount,
|
|
202
|
+
message: json?.message,
|
|
203
|
+
userMessage: json?.userMessage,
|
|
204
|
+
},
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
async function fetchTranscript(input, ctx, deps) {
|
|
208
|
+
if (!input.interviewId) {
|
|
209
|
+
throw new InterviewToolError("action 'transcript' requires interviewId.");
|
|
210
|
+
}
|
|
211
|
+
const response = await call(ctx, 'GET', `/api/interviews/${input.interviewId}`, 'fetch transcript');
|
|
212
|
+
if (!response.ok)
|
|
213
|
+
await throwForResponse(response, deps, 'fetch transcript');
|
|
214
|
+
const json = await readJson(response);
|
|
215
|
+
const interview = json?.interview;
|
|
216
|
+
if (!interview?.id) {
|
|
217
|
+
throw new InterviewToolError('Transcript fetch returned no interview.');
|
|
218
|
+
}
|
|
219
|
+
return {
|
|
220
|
+
content: [{ type: 'text', text: formatTranscript(interview) }],
|
|
221
|
+
_meta: { interview },
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
async function setStatus(input, ctx, deps, status) {
|
|
225
|
+
const action = status === 'completed' ? 'complete' : 'reopen';
|
|
226
|
+
if (!input.interviewId) {
|
|
227
|
+
throw new InterviewToolError(`action '${action}' requires interviewId.`);
|
|
228
|
+
}
|
|
229
|
+
const response = await call(ctx, 'PATCH', `/api/interviews/${input.interviewId}`, `${action} interview`, { status });
|
|
230
|
+
if (!response.ok)
|
|
231
|
+
await throwForResponse(response, deps, `${action} interview`);
|
|
232
|
+
const json = await readJson(response);
|
|
233
|
+
const interview = json?.interview;
|
|
234
|
+
if (!interview?.id) {
|
|
235
|
+
throw new InterviewToolError(`Could not ${action} interview — no interview returned.`);
|
|
236
|
+
}
|
|
237
|
+
const verb = status === 'completed' ? 'Completed' : 'Reopened';
|
|
238
|
+
return {
|
|
239
|
+
content: [{ type: 'text', text: `${verb} interview ${interview.id} (status: ${interview.status ?? status}).` }],
|
|
240
|
+
_meta: { interview },
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
export async function interviewPersona(input, deps) {
|
|
244
|
+
const parsed = InterviewPersonaInputSchema.safeParse(input);
|
|
245
|
+
if (!parsed.success) {
|
|
246
|
+
throw new InterviewToolError(`Invalid input — ${formatZodError(parsed.error)}`);
|
|
247
|
+
}
|
|
248
|
+
const ctx = makeCtx(deps);
|
|
249
|
+
const data = parsed.data;
|
|
250
|
+
switch (data.action) {
|
|
251
|
+
case 'start':
|
|
252
|
+
return startInterview(data, ctx, deps);
|
|
253
|
+
case 'turn':
|
|
254
|
+
return sendTurn(data, ctx, deps);
|
|
255
|
+
case 'transcript':
|
|
256
|
+
return fetchTranscript(data, ctx, deps);
|
|
257
|
+
case 'complete':
|
|
258
|
+
return setStatus(data, ctx, deps, 'completed');
|
|
259
|
+
case 'reopen':
|
|
260
|
+
return setStatus(data, ctx, deps, 'active');
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
//# sourceMappingURL=interview.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interview.js","sourceRoot":"","sources":["../../src/tools/interview.ts"],"names":[],"mappings":"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"}
|