@flytedesk/app-kit 0.3.1 → 0.5.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 +6 -3
- package/dist/auth/index.d.ts +1 -1
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/oidc-client.d.ts +18 -0
- package/dist/auth/oidc-client.js +11 -0
- package/dist/auth/oidc-client.js.map +1 -1
- package/dist/bigquery/client.d.ts +18 -0
- package/dist/bigquery/client.js +37 -0
- package/dist/bigquery/client.js.map +1 -0
- package/dist/bigquery/errors.d.ts +26 -0
- package/dist/bigquery/errors.js +80 -0
- package/dist/bigquery/errors.js.map +1 -0
- package/dist/bigquery/extract.d.ts +15 -0
- package/dist/bigquery/extract.js +88 -0
- package/dist/bigquery/extract.js.map +1 -0
- package/dist/bigquery/index.d.ts +47 -0
- package/dist/bigquery/index.js +46 -0
- package/dist/bigquery/index.js.map +1 -0
- package/dist/bigquery/labels.d.ts +16 -0
- package/dist/bigquery/labels.js +26 -0
- package/dist/bigquery/labels.js.map +1 -0
- package/dist/bigquery/query.d.ts +16 -0
- package/dist/bigquery/query.js +86 -0
- package/dist/bigquery/query.js.map +1 -0
- package/dist/bigquery/types.d.ts +155 -0
- package/dist/bigquery/types.js +15 -0
- package/dist/bigquery/types.js.map +1 -0
- package/dist/chat/callback-secret.d.ts +21 -0
- package/dist/chat/callback-secret.js +33 -0
- package/dist/chat/callback-secret.js.map +1 -0
- package/dist/chat/env.d.ts +27 -0
- package/dist/chat/env.js +44 -0
- package/dist/chat/env.js.map +1 -0
- package/dist/chat/index.d.ts +72 -0
- package/dist/chat/index.js +70 -0
- package/dist/chat/index.js.map +1 -0
- package/dist/chat/launcher.d.ts +43 -0
- package/dist/chat/launcher.js +103 -0
- package/dist/chat/launcher.js.map +1 -0
- package/dist/chat/mcp-protocol.d.ts +96 -0
- package/dist/chat/mcp-protocol.js +200 -0
- package/dist/chat/mcp-protocol.js.map +1 -0
- package/dist/chat/mcp-server.d.ts +17 -0
- package/dist/chat/mcp-server.js +71 -0
- package/dist/chat/mcp-server.js.map +1 -0
- package/dist/chat/types.d.ts +91 -0
- package/dist/chat/types.js +5 -0
- package/dist/chat/types.js.map +1 -0
- package/package.json +10 -1
- package/scripts/pending-release-count.mjs +37 -0
- package/scripts/release.sh +126 -0
- package/scripts/release.test.ts +205 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* danxbot rejected the call. Carries the HTTP status so a caller can tell a
|
|
3
|
+
* misconfiguration (4xx — bad board, unknown profile, revoked token) apart from a
|
|
4
|
+
* worker that is down or wedged (5xx / network), which are different operator
|
|
5
|
+
* problems even though both surface to the user as "the assistant is unavailable".
|
|
6
|
+
*/
|
|
7
|
+
export class DanxbotError extends Error {
|
|
8
|
+
httpStatus;
|
|
9
|
+
constructor(message, httpStatus) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.httpStatus = httpStatus;
|
|
12
|
+
this.name = "DanxbotError";
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
async function danxbotFetch(options, path, init) {
|
|
16
|
+
let response;
|
|
17
|
+
try {
|
|
18
|
+
response = await fetch(`${options.apiUrl}${path}`, {
|
|
19
|
+
method: init.method,
|
|
20
|
+
headers: {
|
|
21
|
+
Authorization: `Bearer ${options.apiToken}`,
|
|
22
|
+
...(init.body !== undefined ? { "Content-Type": "application/json" } : {}),
|
|
23
|
+
},
|
|
24
|
+
body: init.body !== undefined ? JSON.stringify(init.body) : undefined,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
catch (cause) {
|
|
28
|
+
// The worker is unreachable. Deliberately NOT swallowed into a placeholder
|
|
29
|
+
// result: a chat that silently never answers is worse than one that says it is
|
|
30
|
+
// down.
|
|
31
|
+
throw new DanxbotError(`danxbot at ${options.apiUrl} is unreachable: ${cause.message}`, null);
|
|
32
|
+
}
|
|
33
|
+
const text = await response.text();
|
|
34
|
+
if (!response.ok) {
|
|
35
|
+
throw new DanxbotError(`danxbot ${init.method} ${path} returned ${response.status}: ${text}`, response.status);
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
return JSON.parse(text);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
throw new DanxbotError(`danxbot ${init.method} ${path} returned unparseable body: ${text}`, response.status);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Create a launcher bound to one board/profile pair. One instance per consumer app
|
|
46
|
+
* (or per board, for an app dispatching onto more than one) — cheap, holds no
|
|
47
|
+
* mutable state of its own.
|
|
48
|
+
*/
|
|
49
|
+
export function createDanxbotLauncher(options) {
|
|
50
|
+
return {
|
|
51
|
+
/**
|
|
52
|
+
* Dispatch a chat turn. `parentJobId` selects the endpoint: absent starts a
|
|
53
|
+
* fresh Claude session (`/api/launch`), present continues the one that job
|
|
54
|
+
* belongs to (`/api/resume`). Both carry the same `board`/`profile`/`task`/
|
|
55
|
+
* `overlay` shape — the only difference on the wire is the extra `job_id`.
|
|
56
|
+
*/
|
|
57
|
+
async dispatchChatTurn(input) {
|
|
58
|
+
const body = {
|
|
59
|
+
board: options.board,
|
|
60
|
+
profile: options.profile,
|
|
61
|
+
task: input.task,
|
|
62
|
+
overlay: input.overlay,
|
|
63
|
+
...(input.parentJobId ? { job_id: input.parentJobId } : {}),
|
|
64
|
+
};
|
|
65
|
+
const result = await danxbotFetch(options, input.parentJobId ? "/api/resume" : "/api/launch", { method: "POST", body });
|
|
66
|
+
return { jobId: result.job_id, parentJobId: result.parent_job_id };
|
|
67
|
+
},
|
|
68
|
+
async getJobStatus(jobId) {
|
|
69
|
+
const result = await danxbotFetch(options, `/api/status/${encodeURIComponent(jobId)}`, { method: "GET" });
|
|
70
|
+
return {
|
|
71
|
+
jobId: result.job_id,
|
|
72
|
+
status: result.status,
|
|
73
|
+
summary: result.summary ?? "",
|
|
74
|
+
elapsedSeconds: result.elapsed_seconds ?? 0,
|
|
75
|
+
inputTokens: result.input_tokens ?? 0,
|
|
76
|
+
outputTokens: result.output_tokens ?? 0,
|
|
77
|
+
cacheReadInputTokens: result.cache_read_input_tokens ?? 0,
|
|
78
|
+
cacheCreationInputTokens: result.cache_creation_input_tokens ?? 0,
|
|
79
|
+
};
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* danxbot job statuses that mean "this job will never produce anything more".
|
|
85
|
+
* Anything else (including an unknown one — danxbot owns this vocabulary and may
|
|
86
|
+
* grow it) is treated as still in flight, so a status danxbot adds later can never
|
|
87
|
+
* make a consumer declare a live turn dead.
|
|
88
|
+
*/
|
|
89
|
+
const TERMINAL_JOB_STATUSES = new Set([
|
|
90
|
+
"completed",
|
|
91
|
+
"failed",
|
|
92
|
+
"critical_failure",
|
|
93
|
+
"cancelled",
|
|
94
|
+
"aborted",
|
|
95
|
+
]);
|
|
96
|
+
export function isTerminalJobStatus(status) {
|
|
97
|
+
return TERMINAL_JOB_STATUSES.has(status);
|
|
98
|
+
}
|
|
99
|
+
/** A terminal status that is terminal because something went WRONG. */
|
|
100
|
+
export function isFailedJobStatus(status) {
|
|
101
|
+
return isTerminalJobStatus(status) && status !== "completed";
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=launcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"launcher.js","sourceRoot":"","sources":["../../src/chat/launcher.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Model Context Protocol conversation a dispatched chat agent speaks, as pure
|
|
3
|
+
* functions — extracted and generalized from media-planner's
|
|
4
|
+
* `packages/chat-mcp/src/protocol.mjs`.
|
|
5
|
+
*
|
|
6
|
+
* MCP over stdio is JSON-RPC 2.0 in newline-delimited JSON. The surface a client
|
|
7
|
+
* actually needs from a tools-only server is small — `initialize`, `tools/list`,
|
|
8
|
+
* `tools/call`, and `ping` — implemented directly here rather than pulling in the
|
|
9
|
+
* MCP SDK, the same reasoning the original file gave: this protocol is meant to run
|
|
10
|
+
* dependency-free (see mcp-server.ts), so this module imports nothing beyond
|
|
11
|
+
* ./types.js.
|
|
12
|
+
*
|
|
13
|
+
* The ONE thing the original hard-coded that this version generalizes: which tools
|
|
14
|
+
* exist. There, `planner_reply` plus five media-planner-specific tools were all
|
|
15
|
+
* switched on by name inside `tools/call`. Here, `tools/call` dispatches to whichever
|
|
16
|
+
* `AppRegisteredTool` the caller registered — `createReplyTool` below builds the
|
|
17
|
+
* reply tool through that SAME mechanism, so "the reply tool" and "an app's own
|
|
18
|
+
* tool" are not two different code paths, just two entries in the same array. That
|
|
19
|
+
* array is the extension point AK-12 asks for: an app adds `audience_validate`,
|
|
20
|
+
* `audience_sample`, `audience_propose_change`, or whatever else it needs, by
|
|
21
|
+
* passing more `AppRegisteredTool` entries to `createChatMcpProtocol`.
|
|
22
|
+
*/
|
|
23
|
+
import type { AppRegisteredTool } from "./types.js";
|
|
24
|
+
/** The protocol version this server implements and will fall back to. */
|
|
25
|
+
export declare const FALLBACK_PROTOCOL_VERSION = "2024-11-05";
|
|
26
|
+
/** JSON-RPC error codes used here (the subset from the spec that applies). */
|
|
27
|
+
export declare const JSON_RPC: {
|
|
28
|
+
INVALID_PARAMS: number;
|
|
29
|
+
METHOD_NOT_FOUND: number;
|
|
30
|
+
INTERNAL_ERROR: number;
|
|
31
|
+
};
|
|
32
|
+
export interface ChatMcpProtocolOptions {
|
|
33
|
+
/** Advertised in `initialize`'s `serverInfo.name`. */
|
|
34
|
+
serverName: string;
|
|
35
|
+
/** Advertised in `initialize`'s `serverInfo.version`. Defaults to "0.1.0". */
|
|
36
|
+
serverVersion?: string;
|
|
37
|
+
/** Every tool this server exposes — the reply tool (via `createReplyTool`) plus
|
|
38
|
+
* whatever the consuming app registers of its own. Order is preserved in
|
|
39
|
+
* `tools/list`. */
|
|
40
|
+
tools: AppRegisteredTool[];
|
|
41
|
+
}
|
|
42
|
+
export interface ChatMcpProtocol {
|
|
43
|
+
/**
|
|
44
|
+
* Handle one incoming JSON-RPC message. Returns `null` for anything that must not
|
|
45
|
+
* be answered — notifications (which carry no id and by spec get no response).
|
|
46
|
+
* The caller writes whatever is returned and writes nothing otherwise.
|
|
47
|
+
*/
|
|
48
|
+
handleMessage(message: Record<string, unknown>): Promise<Record<string, unknown> | null>;
|
|
49
|
+
}
|
|
50
|
+
export declare function createChatMcpProtocol(options: ChatMcpProtocolOptions): ChatMcpProtocol;
|
|
51
|
+
export interface ChatReplyPayload {
|
|
52
|
+
kind: "progress" | "final";
|
|
53
|
+
text: string;
|
|
54
|
+
packet?: {
|
|
55
|
+
type: string;
|
|
56
|
+
payload: Record<string, unknown>;
|
|
57
|
+
};
|
|
58
|
+
citations?: unknown[];
|
|
59
|
+
}
|
|
60
|
+
export type ReplyDeliverResult = {
|
|
61
|
+
ok: true;
|
|
62
|
+
} | {
|
|
63
|
+
ok: false;
|
|
64
|
+
retryable: boolean;
|
|
65
|
+
message: string;
|
|
66
|
+
};
|
|
67
|
+
export interface ParseReplyArgumentsOptions {
|
|
68
|
+
/** Restrict `packet.type` to this list. Omit to allow any non-empty string —
|
|
69
|
+
* useful when the app validates packet shape downstream of delivery instead
|
|
70
|
+
* (the same choice media-planner's own `/chat/internal/reply` route makes). */
|
|
71
|
+
packetTypes?: string[];
|
|
72
|
+
}
|
|
73
|
+
/** Validate the arguments of a reply-tool call. Exported for direct unit testing,
|
|
74
|
+
* matching the original protocol.mjs's own `parseReplyArguments`. */
|
|
75
|
+
export declare function parseReplyArguments(args: unknown, options?: ParseReplyArgumentsOptions): {
|
|
76
|
+
ok: true;
|
|
77
|
+
value: ChatReplyPayload;
|
|
78
|
+
} | {
|
|
79
|
+
ok: false;
|
|
80
|
+
message: string;
|
|
81
|
+
};
|
|
82
|
+
export interface ReplyToolOptions {
|
|
83
|
+
/** Tool name advertised to the agent. Default "chat_reply". */
|
|
84
|
+
name?: string;
|
|
85
|
+
/** Full description shown to the model — write this in your own product's voice;
|
|
86
|
+
* it is the one thing that tells the agent this is its ONLY way to speak. */
|
|
87
|
+
description: string;
|
|
88
|
+
/** See ParseReplyArgumentsOptions.packetTypes. */
|
|
89
|
+
packetTypes?: string[];
|
|
90
|
+
/** POST (or otherwise deliver) one reply to the app's own callback route. */
|
|
91
|
+
deliver: (reply: ChatReplyPayload) => Promise<ReplyDeliverResult>;
|
|
92
|
+
}
|
|
93
|
+
/** Build the reply tool — the ONE tool every consumer of this module needs, and the
|
|
94
|
+
* one built-in entry alongside whatever `AppRegisteredTool`s the app adds of its
|
|
95
|
+
* own (the extension point). */
|
|
96
|
+
export declare function createReplyTool(options: ReplyToolOptions): AppRegisteredTool;
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/** The protocol version this server implements and will fall back to. */
|
|
2
|
+
export const FALLBACK_PROTOCOL_VERSION = "2024-11-05";
|
|
3
|
+
/** JSON-RPC error codes used here (the subset from the spec that applies). */
|
|
4
|
+
export const JSON_RPC = {
|
|
5
|
+
INVALID_PARAMS: -32602,
|
|
6
|
+
METHOD_NOT_FOUND: -32601,
|
|
7
|
+
INTERNAL_ERROR: -32603,
|
|
8
|
+
};
|
|
9
|
+
function ok(id, result) {
|
|
10
|
+
return { jsonrpc: "2.0", id, result };
|
|
11
|
+
}
|
|
12
|
+
function err(id, code, message) {
|
|
13
|
+
return { jsonrpc: "2.0", id, error: { code, message } };
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* A tool-call outcome that is the AGENT's mistake rather than a transport failure —
|
|
17
|
+
* a bad argument, or a reply sent after the turn already closed. MCP models these as
|
|
18
|
+
* a successful call whose result carries `isError`, which is what puts the message
|
|
19
|
+
* in front of the model so it can correct itself, instead of a JSON-RPC error that
|
|
20
|
+
* surfaces as a broken tool.
|
|
21
|
+
*/
|
|
22
|
+
function toolError(id, message) {
|
|
23
|
+
return ok(id, { content: [{ type: "text", text: message }], isError: true });
|
|
24
|
+
}
|
|
25
|
+
function toolSuccess(id, content) {
|
|
26
|
+
return ok(id, { content });
|
|
27
|
+
}
|
|
28
|
+
export function createChatMcpProtocol(options) {
|
|
29
|
+
const toolsByName = new Map(options.tools.map((tool) => [tool.name, tool]));
|
|
30
|
+
return {
|
|
31
|
+
async handleMessage(message) {
|
|
32
|
+
const method = typeof message.method === "string" ? message.method : "";
|
|
33
|
+
const hasId = message.id !== undefined && message.id !== null;
|
|
34
|
+
const id = hasId ? message.id : null;
|
|
35
|
+
// Notifications carry no id and get no response, per JSON-RPC.
|
|
36
|
+
if (!hasId)
|
|
37
|
+
return null;
|
|
38
|
+
switch (method) {
|
|
39
|
+
case "initialize": {
|
|
40
|
+
const params = (message.params ?? {});
|
|
41
|
+
const requested = params.protocolVersion;
|
|
42
|
+
return ok(id, {
|
|
43
|
+
protocolVersion: typeof requested === "string" ? requested : FALLBACK_PROTOCOL_VERSION,
|
|
44
|
+
capabilities: { tools: {} },
|
|
45
|
+
serverInfo: { name: options.serverName, version: options.serverVersion ?? "0.1.0" },
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
case "tools/list":
|
|
49
|
+
return ok(id, {
|
|
50
|
+
tools: options.tools.map((tool) => ({
|
|
51
|
+
name: tool.name,
|
|
52
|
+
description: tool.description,
|
|
53
|
+
inputSchema: tool.inputSchema,
|
|
54
|
+
})),
|
|
55
|
+
});
|
|
56
|
+
case "tools/call": {
|
|
57
|
+
const params = (message.params ?? {});
|
|
58
|
+
const name = params.name;
|
|
59
|
+
if (typeof name !== "string" || name === "") {
|
|
60
|
+
return err(id, JSON_RPC.INVALID_PARAMS, '`params.name` must be a non-empty string.');
|
|
61
|
+
}
|
|
62
|
+
const tool = toolsByName.get(name);
|
|
63
|
+
if (!tool) {
|
|
64
|
+
return err(id, JSON_RPC.METHOD_NOT_FOUND, `Unknown tool "${name}"`);
|
|
65
|
+
}
|
|
66
|
+
const result = await tool.handler(params.arguments);
|
|
67
|
+
if (result.ok)
|
|
68
|
+
return toolSuccess(id, result.content);
|
|
69
|
+
// A retryable failure is the transport's fault and is reported as a
|
|
70
|
+
// JSON-RPC error; a non-retryable one is the agent's own mistake and is
|
|
71
|
+
// reported as a tool error so the model reads it and corrects itself
|
|
72
|
+
// rather than retrying into the same failure.
|
|
73
|
+
return result.retryable
|
|
74
|
+
? err(id, JSON_RPC.INTERNAL_ERROR, result.message)
|
|
75
|
+
: toolError(id, result.message);
|
|
76
|
+
}
|
|
77
|
+
case "ping":
|
|
78
|
+
return ok(id, {});
|
|
79
|
+
default:
|
|
80
|
+
return err(id, JSON_RPC.METHOD_NOT_FOUND, `Unknown method "${method}"`);
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/** Validate the arguments of a reply-tool call. Exported for direct unit testing,
|
|
86
|
+
* matching the original protocol.mjs's own `parseReplyArguments`. */
|
|
87
|
+
export function parseReplyArguments(args, options = {}) {
|
|
88
|
+
if (args === null || typeof args !== "object" || Array.isArray(args)) {
|
|
89
|
+
return { ok: false, message: "Arguments must be an object with `kind` and `text`." };
|
|
90
|
+
}
|
|
91
|
+
const record = args;
|
|
92
|
+
const { kind, text, packet, citations } = record;
|
|
93
|
+
if (kind !== "progress" && kind !== "final") {
|
|
94
|
+
return { ok: false, message: '`kind` must be either "progress" or "final".' };
|
|
95
|
+
}
|
|
96
|
+
if (typeof text !== "string" || text.trim() === "") {
|
|
97
|
+
return { ok: false, message: "`text` must be a non-empty string." };
|
|
98
|
+
}
|
|
99
|
+
if (packet !== undefined && packet !== null) {
|
|
100
|
+
if (typeof packet !== "object" || Array.isArray(packet)) {
|
|
101
|
+
return { ok: false, message: "`packet` must be an object with `type` and `payload`." };
|
|
102
|
+
}
|
|
103
|
+
const p = packet;
|
|
104
|
+
if (typeof p.type !== "string" || p.type === "") {
|
|
105
|
+
return { ok: false, message: "`packet.type` must be a non-empty string." };
|
|
106
|
+
}
|
|
107
|
+
if (options.packetTypes && !options.packetTypes.includes(p.type)) {
|
|
108
|
+
return {
|
|
109
|
+
ok: false,
|
|
110
|
+
message: `\`packet.type\` must be one of: ${options.packetTypes.join(", ")}.`,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
if (p.payload === null || typeof p.payload !== "object" || Array.isArray(p.payload)) {
|
|
114
|
+
return { ok: false, message: "`packet.payload` must be an object." };
|
|
115
|
+
}
|
|
116
|
+
// A packet on a progress line would never be rendered — reject it rather than
|
|
117
|
+
// dropping it silently and leaving the agent believing it proposed something.
|
|
118
|
+
if (kind === "progress") {
|
|
119
|
+
return { ok: false, message: "A `packet` can only be attached to a `final` reply." };
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (citations !== undefined && citations !== null && !Array.isArray(citations)) {
|
|
123
|
+
return { ok: false, message: "`citations` must be an array." };
|
|
124
|
+
}
|
|
125
|
+
const value = { kind, text };
|
|
126
|
+
if (packet !== undefined && packet !== null)
|
|
127
|
+
value.packet = packet;
|
|
128
|
+
if (citations !== undefined && citations !== null)
|
|
129
|
+
value.citations = citations;
|
|
130
|
+
return { ok: true, value };
|
|
131
|
+
}
|
|
132
|
+
/** Build the reply tool — the ONE tool every consumer of this module needs, and the
|
|
133
|
+
* one built-in entry alongside whatever `AppRegisteredTool`s the app adds of its
|
|
134
|
+
* own (the extension point). */
|
|
135
|
+
export function createReplyTool(options) {
|
|
136
|
+
const name = options.name ?? "chat_reply";
|
|
137
|
+
return {
|
|
138
|
+
name,
|
|
139
|
+
description: options.description,
|
|
140
|
+
inputSchema: {
|
|
141
|
+
type: "object",
|
|
142
|
+
properties: {
|
|
143
|
+
kind: {
|
|
144
|
+
type: "string",
|
|
145
|
+
enum: ["progress", "final"],
|
|
146
|
+
description: '"progress" for a short status line shown while you work (send this immediately, ' +
|
|
147
|
+
'before doing anything else); "final" for your complete answer. Exactly one "final" per turn.',
|
|
148
|
+
},
|
|
149
|
+
text: {
|
|
150
|
+
type: "string",
|
|
151
|
+
description: 'The message. For "final", markdown formatted. For "progress", one short present-tense line.',
|
|
152
|
+
},
|
|
153
|
+
packet: {
|
|
154
|
+
type: "object",
|
|
155
|
+
description: "Optional, and only on a final reply: a structured proposal attached to the answer.",
|
|
156
|
+
properties: {
|
|
157
|
+
type: options.packetTypes ? { type: "string", enum: options.packetTypes } : { type: "string" },
|
|
158
|
+
payload: { type: "object" },
|
|
159
|
+
},
|
|
160
|
+
required: ["type", "payload"],
|
|
161
|
+
},
|
|
162
|
+
citations: {
|
|
163
|
+
type: "array",
|
|
164
|
+
description: "Optional source references shown under a final answer.",
|
|
165
|
+
items: {
|
|
166
|
+
type: "object",
|
|
167
|
+
properties: {
|
|
168
|
+
marker: { type: "number" },
|
|
169
|
+
title: { type: "string" },
|
|
170
|
+
detail: { type: "string" },
|
|
171
|
+
},
|
|
172
|
+
required: ["marker", "title"],
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
required: ["kind", "text"],
|
|
177
|
+
},
|
|
178
|
+
async handler(args) {
|
|
179
|
+
const parsed = parseReplyArguments(args, { packetTypes: options.packetTypes });
|
|
180
|
+
if (!parsed.ok)
|
|
181
|
+
return { ok: false, retryable: false, message: parsed.message };
|
|
182
|
+
const result = await options.deliver(parsed.value);
|
|
183
|
+
if (result.ok) {
|
|
184
|
+
return {
|
|
185
|
+
ok: true,
|
|
186
|
+
content: [
|
|
187
|
+
{
|
|
188
|
+
type: "text",
|
|
189
|
+
text: parsed.value.kind === "final"
|
|
190
|
+
? "Delivered. This turn is now complete — do not send anything further."
|
|
191
|
+
: "Delivered. Continue with the work, then send your final reply.",
|
|
192
|
+
},
|
|
193
|
+
],
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
return { ok: false, retryable: result.retryable, message: result.message };
|
|
197
|
+
},
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
//# sourceMappingURL=mcp-protocol.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-protocol.js","sourceRoot":"","sources":["../../src/chat/mcp-protocol.ts"],"names":[],"mappings":"AA4BA,yEAAyE;AACzE,MAAM,CAAC,MAAM,yBAAyB,GAAG,YAAY,CAAC;AAEtD,8EAA8E;AAC9E,MAAM,CAAC,MAAM,QAAQ,GAAG;IACtB,cAAc,EAAE,CAAC,KAAK;IACtB,gBAAgB,EAAE,CAAC,KAAK;IACxB,cAAc,EAAE,CAAC,KAAK;CACvB,CAAC;AAsBF,SAAS,EAAE,CAAC,EAA0B,EAAE,MAAe;IACrD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC;AACxC,CAAC;AAED,SAAS,GAAG,CAAC,EAA0B,EAAE,IAAY,EAAE,OAAe;IACpE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,CAAC;AAC1D,CAAC;AAED;;;;;;GAMG;AACH,SAAS,SAAS,CAAC,EAA0B,EAAE,OAAe;IAC5D,OAAO,EAAE,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;AAC/E,CAAC;AAED,SAAS,WAAW,CAAC,EAA0B,EAAE,OAA0B;IACzE,OAAO,EAAE,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;AAC7B,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,OAA+B;IACnE,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;IAE5E,OAAO;QACL,KAAK,CAAC,aAAa,CAAC,OAAO;YACzB,MAAM,MAAM,GAAG,OAAO,OAAO,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;YACxE,MAAM,KAAK,GAAG,OAAO,CAAC,EAAE,KAAK,SAAS,IAAI,OAAO,CAAC,EAAE,KAAK,IAAI,CAAC;YAC9D,MAAM,EAAE,GAAG,KAAK,CAAC,CAAC,CAAE,OAAO,CAAC,EAAsB,CAAC,CAAC,CAAC,IAAI,CAAC;YAE1D,+DAA+D;YAC/D,IAAI,CAAC,KAAK;gBAAE,OAAO,IAAI,CAAC;YAExB,QAAQ,MAAM,EAAE,CAAC;gBACf,KAAK,YAAY,CAAC,CAAC,CAAC;oBAClB,MAAM,MAAM,GAAG,CAAC,OAAO,CAAC,MAAM,IAAI,EAAE,CAA4B,CAAC;oBACjE,MAAM,SAAS,GAAG,MAAM,CAAC,eAAe,CAAC;oBACzC,OAAO,EAAE,CAAC,EAAE,EAAE;wBACZ,eAAe,EAAE,OAAO,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,yBAAyB;wBACtF,YAAY,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE;wBAC3B,UAAU,EAAE,EAAE,IAAI,EAAE,OAAO,CAAC,UAAU,EAAE,OAAO,EAAE,OAAO,CAAC,aAAa,IAAI,OAAO,EAAE;qBACpF,CAAC,CAAC;gBACL,CAAC;gBAED,KAAK,YAAY;oBACf,OAAO,EAAE,CAAC,EAAE,EAAE;wBACZ,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;4BAClC,IAAI,EAAE,IAAI,CAAC,IAAI;4BACf,WAAW,EAAE,IAAI,CAAC,WAAW;4BAC7B,WAAW,EAAE,IAAI,CAAC,WAAW;yBAC9B,CAAC,CAAC;qBACJ,CAAC,CAAC;gBAEL,KAAK,YAAY,CAAC,CAAC,CAAC;oBAClB,MAAM,MAAM,GAAG,CAAC,OAAO,CAAC,MAAM,IAAI,EAAE,CAA4B,CAAC;oBACjE,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC;oBACzB,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,EAAE,EAAE,CAAC;wBAC5C,OAAO,GAAG,CAAC,EAAE,EAAE,QAAQ,CAAC,cAAc,EAAE,2CAA2C,CAAC,CAAC;oBACvF,CAAC;oBACD,MAAM,IAAI,GAAG,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;oBACnC,IAAI,CAAC,IAAI,EAAE,CAAC;wBACV,OAAO,GAAG,CAAC,EAAE,EAAE,QAAQ,CAAC,gBAAgB,EAAE,iBAAiB,IAAI,GAAG,CAAC,CAAC;oBACtE,CAAC;oBAED,MAAM,MAAM,GAAuB,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;oBACxE,IAAI,MAAM,CAAC,EAAE;wBAAE,OAAO,WAAW,CAAC,EAAE,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;oBACtD,oEAAoE;oBACpE,wEAAwE;oBACxE,qEAAqE;oBACrE,8CAA8C;oBAC9C,OAAO,MAAM,CAAC,SAAS;wBACrB,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC,OAAO,CAAC;wBAClD,CAAC,CAAC,SAAS,CAAC,EAAE,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;gBACpC,CAAC;gBAED,KAAK,MAAM;oBACT,OAAO,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;gBAEpB;oBACE,OAAO,GAAG,CAAC,EAAE,EAAE,QAAQ,CAAC,gBAAgB,EAAE,mBAAmB,MAAM,GAAG,CAAC,CAAC;YAC5E,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC;AAuBD;sEACsE;AACtE,MAAM,UAAU,mBAAmB,CACjC,IAAa,EACb,UAAsC,EAAE;IAExC,IAAI,IAAI,KAAK,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QACrE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,qDAAqD,EAAE,CAAC;IACvF,CAAC;IACD,MAAM,MAAM,GAAG,IAA+B,CAAC;IAC/C,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,CAAC;IAEjD,IAAI,IAAI,KAAK,UAAU,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;QAC5C,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,8CAA8C,EAAE,CAAC;IAChF,CAAC;IACD,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QACnD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,oCAAoC,EAAE,CAAC;IACtE,CAAC;IACD,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;QAC5C,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YACxD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,uDAAuD,EAAE,CAAC;QACzF,CAAC;QACD,MAAM,CAAC,GAAG,MAAiC,CAAC;QAC5C,IAAI,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ,IAAI,CAAC,CAAC,IAAI,KAAK,EAAE,EAAE,CAAC;YAChD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,2CAA2C,EAAE,CAAC;QAC7E,CAAC;QACD,IAAI,OAAO,CAAC,WAAW,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;YACjE,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,OAAO,EAAE,mCAAmC,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;aAC9E,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,CAAC,OAAO,KAAK,IAAI,IAAI,OAAO,CAAC,CAAC,OAAO,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;YACpF,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,qCAAqC,EAAE,CAAC;QACvE,CAAC;QACD,8EAA8E;QAC9E,8EAA8E;QAC9E,IAAI,IAAI,KAAK,UAAU,EAAE,CAAC;YACxB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,qDAAqD,EAAE,CAAC;QACvF,CAAC;IACH,CAAC;IACD,IAAI,SAAS,KAAK,SAAS,IAAI,SAAS,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC;QAC/E,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,+BAA+B,EAAE,CAAC;IACjE,CAAC;IAED,MAAM,KAAK,GAAqB,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IAC/C,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,KAAK,IAAI;QAAE,KAAK,CAAC,MAAM,GAAG,MAAoC,CAAC;IACjG,IAAI,SAAS,KAAK,SAAS,IAAI,SAAS,KAAK,IAAI;QAAE,KAAK,CAAC,SAAS,GAAG,SAAS,CAAC;IAC/E,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;AAC7B,CAAC;AAcD;;iCAEiC;AACjC,MAAM,UAAU,eAAe,CAAC,OAAyB;IACvD,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,YAAY,CAAC;IAC1C,OAAO;QACL,IAAI;QACJ,WAAW,EAAE,OAAO,CAAC,WAAW;QAChC,WAAW,EAAE;YACX,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE;gBACV,IAAI,EAAE;oBACJ,IAAI,EAAE,QAAQ;oBACd,IAAI,EAAE,CAAC,UAAU,EAAE,OAAO,CAAC;oBAC3B,WAAW,EACT,kFAAkF;wBAClF,8FAA8F;iBACjG;gBACD,IAAI,EAAE;oBACJ,IAAI,EAAE,QAAQ;oBACd,WAAW,EACT,6FAA6F;iBAChG;gBACD,MAAM,EAAE;oBACN,IAAI,EAAE,QAAQ;oBACd,WAAW,EACT,oFAAoF;oBACtF,UAAU,EAAE;wBACV,IAAI,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE;wBAC9F,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;qBAC5B;oBACD,QAAQ,EAAE,CAAC,MAAM,EAAE,SAAS,CAAC;iBAC9B;gBACD,SAAS,EAAE;oBACT,IAAI,EAAE,OAAO;oBACb,WAAW,EAAE,wDAAwD;oBACrE,KAAK,EAAE;wBACL,IAAI,EAAE,QAAQ;wBACd,UAAU,EAAE;4BACV,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;4BAC1B,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;4BACzB,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;yBAC3B;wBACD,QAAQ,EAAE,CAAC,QAAQ,EAAE,OAAO,CAAC;qBAC9B;iBACF;aACF;YACD,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC;SAC3B;QACD,KAAK,CAAC,OAAO,CAAC,IAAa;YACzB,MAAM,MAAM,GAAG,mBAAmB,CAAC,IAAI,EAAE,EAAE,WAAW,EAAE,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;YAC/E,IAAI,CAAC,MAAM,CAAC,EAAE;gBAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC;YAEhF,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YACnD,IAAI,MAAM,CAAC,EAAE,EAAE,CAAC;gBACd,OAAO;oBACL,EAAE,EAAE,IAAI;oBACR,OAAO,EAAE;wBACP;4BACE,IAAI,EAAE,MAAM;4BACZ,IAAI,EACF,MAAM,CAAC,KAAK,CAAC,IAAI,KAAK,OAAO;gCAC3B,CAAC,CAAC,sEAAsE;gCACxE,CAAC,CAAC,gEAAgE;yBACvE;qBACF;iBACF,CAAC;YACJ,CAAC;YACD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC;QAC7E,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ChatMcpProtocol } from "./mcp-protocol.js";
|
|
2
|
+
export interface ChatMcpStdioServerOptions {
|
|
3
|
+
protocol: ChatMcpProtocol;
|
|
4
|
+
/** Defaults to `process.stdin`. Overridable for tests. */
|
|
5
|
+
input?: NodeJS.ReadableStream;
|
|
6
|
+
/** Defaults to `process.stdout`. Overridable for tests. */
|
|
7
|
+
output?: NodeJS.WritableStream;
|
|
8
|
+
/** Defaults to writing to `process.stderr`. Overridable for tests. */
|
|
9
|
+
onError?: (message: string) => void;
|
|
10
|
+
}
|
|
11
|
+
export interface ChatMcpStdioServer {
|
|
12
|
+
/** Start reading lines from `input`. Registers listeners and returns immediately —
|
|
13
|
+
* it does not block until the stream closes; the process stays alive on its own
|
|
14
|
+
* (stdin being read is enough to keep Node running) until `input` closes. */
|
|
15
|
+
start(): void;
|
|
16
|
+
}
|
|
17
|
+
export declare function createChatMcpStdioServer(options: ChatMcpStdioServerOptions): ChatMcpStdioServer;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The stdio transport for a chat MCP server — extracted and generalized from
|
|
3
|
+
* media-planner's `packages/chat-mcp/src/server.mjs`'s I/O loop (that file's own
|
|
4
|
+
* `deliver`/`read`/`write` HTTP helpers stay app-specific; only the newline-delimited
|
|
5
|
+
* JSON-RPC plumbing moves here).
|
|
6
|
+
*
|
|
7
|
+
* Deliberately built on Node builtins ONLY (`node:readline`) — no runtime dependency
|
|
8
|
+
* on anything else in this package or elsewhere. The reason: danxbot runs a
|
|
9
|
+
* dispatched agent's MCP server straight out of a plain git clone with no install
|
|
10
|
+
* step, so whatever script actually executes there must be runnable with nothing
|
|
11
|
+
* more than `node <file>`. This module (and mcp-protocol.ts, which it composes with)
|
|
12
|
+
* satisfy that constraint structurally; wiring a specific app's checkout so the
|
|
13
|
+
* *published* `@flytedesk/app-kit` package is actually resolvable from that
|
|
14
|
+
* unbuilt clone is that app's own integration concern, not this module's.
|
|
15
|
+
*/
|
|
16
|
+
import { createInterface } from "node:readline";
|
|
17
|
+
export function createChatMcpStdioServer(options) {
|
|
18
|
+
const input = options.input ?? process.stdin;
|
|
19
|
+
const output = options.output ?? process.stdout;
|
|
20
|
+
const onError = options.onError ?? ((message) => process.stderr.write(`${message}\n`));
|
|
21
|
+
function writeLine(payload) {
|
|
22
|
+
output.write(`${JSON.stringify(payload)}\n`);
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
start() {
|
|
26
|
+
const rl = createInterface({ input });
|
|
27
|
+
rl.on("line", (line) => {
|
|
28
|
+
const trimmed = line.trim();
|
|
29
|
+
if (trimmed === "")
|
|
30
|
+
return;
|
|
31
|
+
let message;
|
|
32
|
+
try {
|
|
33
|
+
message = JSON.parse(trimmed);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
// Unparseable input has no id to answer against, so there is nothing to
|
|
37
|
+
// reply to — report it and keep the stream alive rather than tearing down
|
|
38
|
+
// the session.
|
|
39
|
+
onError(`ignoring unparseable line: ${trimmed}`);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
// Handled without awaiting so a slow delivery cannot block the next
|
|
43
|
+
// request; JSON-RPC responses carry their own id, so out-of-order
|
|
44
|
+
// completion is fine.
|
|
45
|
+
void options.protocol
|
|
46
|
+
.handleMessage(message)
|
|
47
|
+
.then((response) => {
|
|
48
|
+
if (response)
|
|
49
|
+
writeLine(response);
|
|
50
|
+
})
|
|
51
|
+
.catch((cause) => {
|
|
52
|
+
const err = cause;
|
|
53
|
+
onError(`handler failed: ${err?.stack ?? String(cause)}`);
|
|
54
|
+
const id = message.id;
|
|
55
|
+
if (id !== undefined && id !== null) {
|
|
56
|
+
writeLine({
|
|
57
|
+
jsonrpc: "2.0",
|
|
58
|
+
id,
|
|
59
|
+
error: { code: -32603, message: `Internal error: ${err?.message ?? String(cause)}` },
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
// stdin closing is the client going away — nothing further to do; the
|
|
65
|
+
// process's own exit is the caller's concern (see this module's usage in a
|
|
66
|
+
// package `bin`/entrypoint script), not this function's.
|
|
67
|
+
rl.on("close", () => { });
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=mcp-server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-server.js","sourceRoot":"","sources":["../../src/chat/mcp-server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAoBhD,MAAM,UAAU,wBAAwB,CAAC,OAAkC;IACzE,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,OAAO,CAAC,KAAK,CAAC;IAC7C,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC;IAChD,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,CAAC,CAAC,OAAe,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,OAAO,IAAI,CAAC,CAAC,CAAC;IAE/F,SAAS,SAAS,CAAC,OAAgC;QACjD,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,CAAC;IAED,OAAO;QACL,KAAK;YACH,MAAM,EAAE,GAAG,eAAe,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;YAEtC,EAAE,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;gBACrB,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;gBAC5B,IAAI,OAAO,KAAK,EAAE;oBAAE,OAAO;gBAE3B,IAAI,OAAgC,CAAC;gBACrC,IAAI,CAAC;oBACH,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAA4B,CAAC;gBAC3D,CAAC;gBAAC,MAAM,CAAC;oBACP,wEAAwE;oBACxE,0EAA0E;oBAC1E,eAAe;oBACf,OAAO,CAAC,8BAA8B,OAAO,EAAE,CAAC,CAAC;oBACjD,OAAO;gBACT,CAAC;gBAED,oEAAoE;gBACpE,kEAAkE;gBAClE,sBAAsB;gBACtB,KAAK,OAAO,CAAC,QAAQ;qBAClB,aAAa,CAAC,OAAO,CAAC;qBACtB,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE;oBACjB,IAAI,QAAQ;wBAAE,SAAS,CAAC,QAAQ,CAAC,CAAC;gBACpC,CAAC,CAAC;qBACD,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;oBACxB,MAAM,GAAG,GAAG,KAAyD,CAAC;oBACtE,OAAO,CAAC,mBAAmB,GAAG,EAAE,KAAK,IAAI,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;oBAC1D,MAAM,EAAE,GAAG,OAAO,CAAC,EAAE,CAAC;oBACtB,IAAI,EAAE,KAAK,SAAS,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC;wBACpC,SAAS,CAAC;4BACR,OAAO,EAAE,KAAK;4BACd,EAAE;4BACF,KAAK,EAAE,EAAE,IAAI,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,mBAAmB,GAAG,EAAE,OAAO,IAAI,MAAM,CAAC,KAAK,CAAC,EAAE,EAAE;yBACrF,CAAC,CAAC;oBACL,CAAC;gBACH,CAAC,CAAC,CAAC;YACP,CAAC,CAAC,CAAC;YAEH,sEAAsE;YACtE,2EAA2E;YAC3E,yDAAyD;YACzD,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QAC3B,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types for @flytedesk/app-kit/chat — see index.ts for the module overview.
|
|
3
|
+
*/
|
|
4
|
+
/** danxbot's own launch/resume response shape (`POST /api/launch` / `/api/resume`). */
|
|
5
|
+
export interface DanxbotDispatchResult {
|
|
6
|
+
jobId: string;
|
|
7
|
+
parentJobId?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface DanxbotJobStatus {
|
|
10
|
+
jobId: string;
|
|
11
|
+
/** danxbot's own vocabulary — "running", "completed", "failed", "critical_failure", … */
|
|
12
|
+
status: string;
|
|
13
|
+
summary: string;
|
|
14
|
+
elapsedSeconds: number;
|
|
15
|
+
/** Real usage off danxbot's own wire, never estimated. Defaulted to 0 only for a
|
|
16
|
+
* field genuinely ABSENT from an otherwise-successful response (defensive parsing
|
|
17
|
+
* of someone else's JSON) — a failed `getJobStatus` call throws, it never returns
|
|
18
|
+
* zeros to paper over a failure. */
|
|
19
|
+
inputTokens: number;
|
|
20
|
+
outputTokens: number;
|
|
21
|
+
cacheReadInputTokens: number;
|
|
22
|
+
cacheCreationInputTokens: number;
|
|
23
|
+
}
|
|
24
|
+
export interface DanxbotLauncherOptions {
|
|
25
|
+
/** Base URL of the danxbot worker's HTTP API, e.g. "http://localhost:5555". */
|
|
26
|
+
apiUrl: string;
|
|
27
|
+
/** Bearer token danxbot's `/api/launch`/`/api/resume`/`/api/status` expect. */
|
|
28
|
+
apiToken: string;
|
|
29
|
+
/** The danxbot board this app's chat turns dispatch onto, e.g. "sms-app:sms-app-main". */
|
|
30
|
+
board: string;
|
|
31
|
+
/** The danxbot agent profile to dispatch — the board-level chat profile the
|
|
32
|
+
* consuming app registered (see this module's README section on wiring a
|
|
33
|
+
* `.danxbot/config` profile override), NOT danxbot's own per-card `board-chat`. */
|
|
34
|
+
profile: string;
|
|
35
|
+
}
|
|
36
|
+
export interface DanxbotLauncher {
|
|
37
|
+
/** Dispatch one chat turn — see dispatchChatTurn's own doc comment for the
|
|
38
|
+
* launch-vs-resume branching. */
|
|
39
|
+
dispatchChatTurn(input: {
|
|
40
|
+
task: string;
|
|
41
|
+
overlay: Record<string, string>;
|
|
42
|
+
parentJobId?: string | null;
|
|
43
|
+
}): Promise<DanxbotDispatchResult>;
|
|
44
|
+
getJobStatus(jobId: string): Promise<DanxbotJobStatus>;
|
|
45
|
+
}
|
|
46
|
+
/** One MCP tool-result content block — the two kinds this module's tools ever return. */
|
|
47
|
+
export type ChatToolContent = {
|
|
48
|
+
type: "text";
|
|
49
|
+
text: string;
|
|
50
|
+
} | {
|
|
51
|
+
type: "image";
|
|
52
|
+
data: string;
|
|
53
|
+
mimeType: string;
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* The outcome of one tool call, common to the built-in reply tool and every
|
|
57
|
+
* app-registered tool. Mirrors the retryable/non-retryable split the underlying HTTP
|
|
58
|
+
* call already makes: a transport failure (network error, 5xx) is worth the agent
|
|
59
|
+
* retrying, while a 4xx/validation failure is the agent's own mistake to correct, so
|
|
60
|
+
* it is reported as a tool error the model reads rather than a broken tool.
|
|
61
|
+
*/
|
|
62
|
+
export type ChatToolCallResult = {
|
|
63
|
+
ok: true;
|
|
64
|
+
content: ChatToolContent[];
|
|
65
|
+
} | {
|
|
66
|
+
ok: false;
|
|
67
|
+
retryable: boolean;
|
|
68
|
+
message: string;
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* One tool an app registers on top of the shared chat MCP server — the extension
|
|
72
|
+
* point AK-12 exists to provide. `handler` receives the raw `tools/call` arguments
|
|
73
|
+
* (validating them, including reporting a bad argument as a non-retryable
|
|
74
|
+
* `ChatToolCallResult`, is the tool's own job — the same contract MCP itself uses:
|
|
75
|
+
* arguments are untyped until a specific tool decides what shape it needs).
|
|
76
|
+
*/
|
|
77
|
+
export interface AppRegisteredTool {
|
|
78
|
+
name: string;
|
|
79
|
+
description: string;
|
|
80
|
+
/** JSON Schema, exactly as advertised in `tools/list`. */
|
|
81
|
+
inputSchema: Record<string, unknown>;
|
|
82
|
+
handler: (args: unknown) => Promise<ChatToolCallResult>;
|
|
83
|
+
}
|
|
84
|
+
/** The env contract every app wiring this module in shares — see env.ts. */
|
|
85
|
+
export interface ChatEnv {
|
|
86
|
+
danxbotApiUrl: string;
|
|
87
|
+
danxbotBoardId: string;
|
|
88
|
+
danxbotApiToken: string;
|
|
89
|
+
danxbotCallbackApiUrl: string;
|
|
90
|
+
danxbotCallbackSecret: string;
|
|
91
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/chat/types.ts"],"names":[],"mappings":"AAAA;;GAEG"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flytedesk/app-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Shared platform kit for flytedesk apps: flytedesk-id auth (BFF/OIDC client) and a Postgres-native trace/audit layer.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -41,6 +41,10 @@
|
|
|
41
41
|
"types": "./dist/trace/index.d.ts",
|
|
42
42
|
"import": "./dist/trace/index.js"
|
|
43
43
|
},
|
|
44
|
+
"./bigquery": {
|
|
45
|
+
"types": "./dist/bigquery/index.d.ts",
|
|
46
|
+
"import": "./dist/bigquery/index.js"
|
|
47
|
+
},
|
|
44
48
|
"./rate-limit": {
|
|
45
49
|
"types": "./dist/rate-limit/index.d.ts",
|
|
46
50
|
"import": "./dist/rate-limit/index.js"
|
|
@@ -52,6 +56,10 @@
|
|
|
52
56
|
"./flags": {
|
|
53
57
|
"types": "./dist/flags/index.d.ts",
|
|
54
58
|
"import": "./dist/flags/index.js"
|
|
59
|
+
},
|
|
60
|
+
"./chat": {
|
|
61
|
+
"types": "./dist/chat/index.d.ts",
|
|
62
|
+
"import": "./dist/chat/index.js"
|
|
55
63
|
}
|
|
56
64
|
},
|
|
57
65
|
"scripts": {
|
|
@@ -64,6 +72,7 @@
|
|
|
64
72
|
"lint": "eslint .",
|
|
65
73
|
"format": "prettier --check .",
|
|
66
74
|
"check:exports": "publint && attw --pack . --profile esm-only",
|
|
75
|
+
"check": "npm run lint && npm run typecheck && npm run build && npm test && npm run test:integration && npm run check:exports",
|
|
67
76
|
"changeset": "changeset",
|
|
68
77
|
"version-packages": "changeset version",
|
|
69
78
|
"release": "npm run build && npm run check:exports && changeset publish"
|