@yak-io/rest 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters.d.ts +21 -2
- package/dist/adapters.d.ts.map +1 -1
- package/dist/index.cjs +89 -0
- package/dist/index.cjs.map +7 -0
- package/dist/index.js +66 -2
- package/dist/index.js.map +7 -0
- package/package.json +5 -4
- package/dist/adapters.js +0 -68
package/dist/adapters.d.ts
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import type { RESTRequest, ToolAdapter } from "@yak-io/javascript";
|
|
2
|
+
/** An OpenAPI spec as an object or a JSON string. */
|
|
3
|
+
type RESTSpec = Record<string, unknown> | string;
|
|
2
4
|
/** Config for {@link createRESTToolAdapter}. */
|
|
3
5
|
export type RESTToolAdapterConfig = {
|
|
4
6
|
/** Spec name — the tool is exposed to the LLM as `rest_<name>`. */
|
|
5
7
|
name: string;
|
|
6
|
-
/**
|
|
7
|
-
|
|
8
|
+
/**
|
|
9
|
+
* OpenAPI spec as an object or JSON string, or a resolver that returns it (sync or async). Use a
|
|
10
|
+
* resolver to fetch the spec lazily: it runs once on first `getTools()` and the result is cached,
|
|
11
|
+
* so the fetch is paid lazily, not at construction. A literal spec is embedded eagerly. Embedded
|
|
12
|
+
* in the tool description so the LLM can author requests.
|
|
13
|
+
*/
|
|
14
|
+
spec: RESTSpec | (() => RESTSpec | Promise<RESTSpec>);
|
|
8
15
|
/**
|
|
9
16
|
* Execute the LLM-authored REST request with YOUR client. Yak builds the tool and the request
|
|
10
17
|
* shape (method / path / query / body) but never makes the call itself — your client owns the
|
|
@@ -33,6 +40,18 @@ export type RESTToolAdapterConfig = {
|
|
|
33
40
|
* execute: (req) => myApiClient(req.method, req.path, { query: req.query, body: req.body }),
|
|
34
41
|
* });
|
|
35
42
|
* ```
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```ts
|
|
46
|
+
* // Lazy: fetch the OpenAPI spec once, on first use. The factory stays synchronous — the fetch
|
|
47
|
+
* // is paid when the toolset first materializes its tools, then cached.
|
|
48
|
+
* const billing = createRESTToolAdapter({
|
|
49
|
+
* name: "billing",
|
|
50
|
+
* spec: async () => (await fetch("/openapi.json")).json(),
|
|
51
|
+
* execute: (req) => myApiClient(req.method, req.path, { query: req.query, body: req.body }),
|
|
52
|
+
* });
|
|
53
|
+
* ```
|
|
36
54
|
*/
|
|
37
55
|
export declare function createRESTToolAdapter(config: RESTToolAdapterConfig): ToolAdapter;
|
|
56
|
+
export {};
|
|
38
57
|
//# sourceMappingURL=adapters.d.ts.map
|
package/dist/adapters.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adapters.d.ts","sourceRoot":"","sources":["../src/adapters.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAc,WAAW,EAAE,WAAW,EAAkB,MAAM,oBAAoB,CAAC;AAE/F,gDAAgD;AAChD,MAAM,MAAM,qBAAqB,GAAG;IAClC,mEAAmE;IACnE,IAAI,EAAE,MAAM,CAAC;IACb
|
|
1
|
+
{"version":3,"file":"adapters.d.ts","sourceRoot":"","sources":["../src/adapters.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAc,WAAW,EAAE,WAAW,EAAkB,MAAM,oBAAoB,CAAC;AAE/F,qDAAqD;AACrD,KAAK,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC;AAEjD,gDAAgD;AAChD,MAAM,MAAM,qBAAqB,GAAG;IAClC,mEAAmE;IACnE,IAAI,EAAE,MAAM,CAAC;IACb;;;;;OAKG;IACH,IAAI,EAAE,QAAQ,GAAG,CAAC,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;IACtD;;;;;;OAMG;IACH,OAAO,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9D,4DAA4D;IAC5D,EAAE,CAAC,EAAE,MAAM,CAAC;CACb,CAAC;AA2BF;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,qBAAqB,GAAG,WAAW,CAoDhF"}
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
createRESTToolAdapter: () => createRESTToolAdapter
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(index_exports);
|
|
26
|
+
|
|
27
|
+
// src/adapters.ts
|
|
28
|
+
var REST_INPUT_SCHEMA = {
|
|
29
|
+
type: "object",
|
|
30
|
+
properties: {
|
|
31
|
+
method: {
|
|
32
|
+
type: "string",
|
|
33
|
+
enum: ["GET", "POST", "PUT", "PATCH", "DELETE"],
|
|
34
|
+
description: "HTTP method for the request."
|
|
35
|
+
},
|
|
36
|
+
path: { type: "string", description: "API path (e.g., '/users/123')." },
|
|
37
|
+
query: {
|
|
38
|
+
type: "object",
|
|
39
|
+
description: "Query parameters as key-value pairs.",
|
|
40
|
+
additionalProperties: { type: "string" }
|
|
41
|
+
},
|
|
42
|
+
body: { description: "Request body for POST/PUT/PATCH requests." }
|
|
43
|
+
},
|
|
44
|
+
required: ["method", "path"],
|
|
45
|
+
additionalProperties: false
|
|
46
|
+
};
|
|
47
|
+
function createRESTToolAdapter(config) {
|
|
48
|
+
const toolName = `rest_${config.name}`;
|
|
49
|
+
const buildTool = (spec) => {
|
|
50
|
+
const specStr = typeof spec === "string" ? spec : JSON.stringify(spec);
|
|
51
|
+
return {
|
|
52
|
+
name: toolName,
|
|
53
|
+
description: `Make a REST API call to the ${config.name} API. Based on the OpenAPI spec below, generate the appropriate request with method, path, query params, and body.
|
|
54
|
+
|
|
55
|
+
OpenAPI spec:
|
|
56
|
+
\`\`\`json
|
|
57
|
+
${specStr}
|
|
58
|
+
\`\`\``,
|
|
59
|
+
inputSchema: REST_INPUT_SCHEMA
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
const eager = typeof config.spec === "function" ? void 0 : [buildTool(config.spec)];
|
|
63
|
+
let pending;
|
|
64
|
+
return {
|
|
65
|
+
id: config.id ?? toolName,
|
|
66
|
+
getTools: () => {
|
|
67
|
+
if (eager) return eager;
|
|
68
|
+
if (!pending) {
|
|
69
|
+
const resolve = config.spec;
|
|
70
|
+
pending = Promise.resolve().then(resolve).then((spec) => [buildTool(spec)]);
|
|
71
|
+
pending.catch(() => {
|
|
72
|
+
pending = void 0;
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
return pending;
|
|
76
|
+
},
|
|
77
|
+
ownsTool: (name) => name === toolName,
|
|
78
|
+
execute: async (_name, args) => {
|
|
79
|
+
const request = args ?? {};
|
|
80
|
+
return config.execute({
|
|
81
|
+
method: request.method,
|
|
82
|
+
path: request.path,
|
|
83
|
+
query: request.query,
|
|
84
|
+
body: request.body
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/index.ts", "../src/adapters.ts"],
|
|
4
|
+
"sourcesContent": ["// REST/OpenAPI tool adapter for the yak client toolset\nexport { createRESTToolAdapter } from \"./adapters.js\";\nexport type { RESTToolAdapterConfig } from \"./adapters.js\";\n\n// Shared types from core\nexport type { RESTRequest, ToolAdapter, ToolDefinition } from \"@yak-io/javascript\";\n", "import type { JSONSchema, RESTRequest, ToolAdapter, ToolDefinition } from \"@yak-io/javascript\";\n\n/** An OpenAPI spec as an object or a JSON string. */\ntype RESTSpec = Record<string, unknown> | string;\n\n/** Config for {@link createRESTToolAdapter}. */\nexport type RESTToolAdapterConfig = {\n /** Spec name \u2014 the tool is exposed to the LLM as `rest_<name>`. */\n name: string;\n /**\n * OpenAPI spec as an object or JSON string, or a resolver that returns it (sync or async). Use a\n * resolver to fetch the spec lazily: it runs once on first `getTools()` and the result is cached,\n * so the fetch is paid lazily, not at construction. A literal spec is embedded eagerly. Embedded\n * in the tool description so the LLM can author requests.\n */\n spec: RESTSpec | (() => RESTSpec | Promise<RESTSpec>);\n /**\n * Execute the LLM-authored REST request with YOUR client. Yak builds the tool and the request\n * shape (method / path / query / body) but never makes the call itself \u2014 your client owns the\n * base URL, auth, transport, and error handling. Use whatever you already have (a configured\n * `fetch` wrapper, axios, your app's API client, \u2026). Return the result the assistant should\n * see; throw to surface an error to the model.\n */\n execute: (request: RESTRequest) => unknown | Promise<unknown>;\n /** Stable id for diagnostics. Defaults to the tool name. */\n id?: string;\n};\n\n/**\n * Generic input schema for a REST tool. The LLM authors method/path/query/body;\n * the adapter hands them to your `execute` client. Do NOT inject `_reason` here \u2014 the\n * chat-ui / voice runtimes inject it when registering the tool with the model.\n */\nconst REST_INPUT_SCHEMA: JSONSchema = {\n type: \"object\",\n properties: {\n method: {\n type: \"string\",\n enum: [\"GET\", \"POST\", \"PUT\", \"PATCH\", \"DELETE\"],\n description: \"HTTP method for the request.\",\n },\n path: { type: \"string\", description: \"API path (e.g., '/users/123').\" },\n query: {\n type: \"object\",\n description: \"Query parameters as key-value pairs.\",\n additionalProperties: { type: \"string\" },\n },\n body: { description: \"Request body for POST/PUT/PATCH requests.\" },\n },\n required: [\"method\", \"path\"],\n additionalProperties: false,\n};\n\n/**\n * Create a REST {@link ToolAdapter}. Compose it into a {@link createYakToolset} so the generated\n * `rest_<name>` tool joins the single merged manifest + `onToolCall` funnel (and therefore\n * surfaces through `onToolCallComplete` / `useYakToolEvent`).\n *\n * The model authors a request from the OpenAPI spec you provide; the adapter hands it to your\n * `execute` callback, which runs it with whatever client you already use. Yak never constructs\n * the HTTP call \u2014 your client owns the base URL, auth, and transport.\n *\n * @example\n * ```ts\n * const billing = createRESTToolAdapter({\n * name: \"billing\",\n * spec: openapiSpec,\n * execute: (req) => myApiClient(req.method, req.path, { query: req.query, body: req.body }),\n * });\n * ```\n *\n * @example\n * ```ts\n * // Lazy: fetch the OpenAPI spec once, on first use. The factory stays synchronous \u2014 the fetch\n * // is paid when the toolset first materializes its tools, then cached.\n * const billing = createRESTToolAdapter({\n * name: \"billing\",\n * spec: async () => (await fetch(\"/openapi.json\")).json(),\n * execute: (req) => myApiClient(req.method, req.path, { query: req.query, body: req.body }),\n * });\n * ```\n */\nexport function createRESTToolAdapter(config: RESTToolAdapterConfig): ToolAdapter {\n const toolName = `rest_${config.name}`;\n\n const buildTool = (spec: RESTSpec): ToolDefinition => {\n // Minified, not pretty-printed: the model reads compact JSON identically, and the\n // spec is embedded verbatim in the tool description sent to the LLM on every turn \u2014\n // dropping the indentation/newlines is a free token saving for large specs.\n const specStr = typeof spec === \"string\" ? spec : JSON.stringify(spec);\n return {\n name: toolName,\n description:\n `Make a REST API call to the ${config.name} API. Based on the OpenAPI spec below, ` +\n \"generate the appropriate request with method, path, query params, and body.\" +\n `\\n\\nOpenAPI spec:\\n\\`\\`\\`json\\n${specStr}\\n\\`\\`\\``,\n inputSchema: REST_INPUT_SCHEMA,\n };\n };\n\n // A literal spec (object or string) builds the tool eagerly. A resolver runs once on first\n // getTools() and the [tool] result is cached; a rejection is NOT cached, so a later getTools()\n // can retry \u2014 a transient spec-fetch failure doesn't wedge the toolset.\n const eager = typeof config.spec === \"function\" ? undefined : [buildTool(config.spec)];\n let pending: Promise<ToolDefinition[]> | undefined;\n\n return {\n id: config.id ?? toolName,\n getTools: () => {\n if (eager) return eager;\n if (!pending) {\n const resolve = config.spec as () => RESTSpec | Promise<RESTSpec>;\n // Promise.resolve().then(resolve) so a synchronous throw also surfaces as a rejection.\n pending = Promise.resolve()\n .then(resolve)\n .then((spec) => [buildTool(spec)]);\n pending.catch(() => {\n pending = undefined; // don't cache failures \u2014 allow retry on the next getTools()\n });\n }\n return pending;\n },\n ownsTool: (name) => name === toolName,\n execute: async (_name, args) => {\n const request = (args ?? {}) as RESTRequest;\n // Hand a clean request to the caller's client \u2014 never forward the runtime-injected `_reason`.\n return config.execute({\n method: request.method,\n path: request.path,\n query: request.query,\n body: request.body,\n });\n },\n };\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACiCA,IAAM,oBAAgC;AAAA,EACpC,MAAM;AAAA,EACN,YAAY;AAAA,IACV,QAAQ;AAAA,MACN,MAAM;AAAA,MACN,MAAM,CAAC,OAAO,QAAQ,OAAO,SAAS,QAAQ;AAAA,MAC9C,aAAa;AAAA,IACf;AAAA,IACA,MAAM,EAAE,MAAM,UAAU,aAAa,iCAAiC;AAAA,IACtE,OAAO;AAAA,MACL,MAAM;AAAA,MACN,aAAa;AAAA,MACb,sBAAsB,EAAE,MAAM,SAAS;AAAA,IACzC;AAAA,IACA,MAAM,EAAE,aAAa,4CAA4C;AAAA,EACnE;AAAA,EACA,UAAU,CAAC,UAAU,MAAM;AAAA,EAC3B,sBAAsB;AACxB;AA+BO,SAAS,sBAAsB,QAA4C;AAChF,QAAM,WAAW,QAAQ,OAAO,IAAI;AAEpC,QAAM,YAAY,CAAC,SAAmC;AAIpD,UAAM,UAAU,OAAO,SAAS,WAAW,OAAO,KAAK,UAAU,IAAI;AACrE,WAAO;AAAA,MACL,MAAM;AAAA,MACN,aACE,+BAA+B,OAAO,IAAI;AAAA;AAAA;AAAA;AAAA,EAER,OAAO;AAAA;AAAA,MAC3C,aAAa;AAAA,IACf;AAAA,EACF;AAKA,QAAM,QAAQ,OAAO,OAAO,SAAS,aAAa,SAAY,CAAC,UAAU,OAAO,IAAI,CAAC;AACrF,MAAI;AAEJ,SAAO;AAAA,IACL,IAAI,OAAO,MAAM;AAAA,IACjB,UAAU,MAAM;AACd,UAAI,MAAO,QAAO;AAClB,UAAI,CAAC,SAAS;AACZ,cAAM,UAAU,OAAO;AAEvB,kBAAU,QAAQ,QAAQ,EACvB,KAAK,OAAO,EACZ,KAAK,CAAC,SAAS,CAAC,UAAU,IAAI,CAAC,CAAC;AACnC,gBAAQ,MAAM,MAAM;AAClB,oBAAU;AAAA,QACZ,CAAC;AAAA,MACH;AACA,aAAO;AAAA,IACT;AAAA,IACA,UAAU,CAAC,SAAS,SAAS;AAAA,IAC7B,SAAS,OAAO,OAAO,SAAS;AAC9B,YAAM,UAAW,QAAQ,CAAC;AAE1B,aAAO,OAAO,QAAQ;AAAA,QACpB,QAAQ,QAAQ;AAAA,QAChB,MAAM,QAAQ;AAAA,QACd,OAAO,QAAQ;AAAA,QACf,MAAM,QAAQ;AAAA,MAChB,CAAC;AAAA,IACH;AAAA,EACF;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,66 @@
|
|
|
1
|
-
//
|
|
2
|
-
|
|
1
|
+
// src/adapters.ts
|
|
2
|
+
var REST_INPUT_SCHEMA = {
|
|
3
|
+
type: "object",
|
|
4
|
+
properties: {
|
|
5
|
+
method: {
|
|
6
|
+
type: "string",
|
|
7
|
+
enum: ["GET", "POST", "PUT", "PATCH", "DELETE"],
|
|
8
|
+
description: "HTTP method for the request."
|
|
9
|
+
},
|
|
10
|
+
path: { type: "string", description: "API path (e.g., '/users/123')." },
|
|
11
|
+
query: {
|
|
12
|
+
type: "object",
|
|
13
|
+
description: "Query parameters as key-value pairs.",
|
|
14
|
+
additionalProperties: { type: "string" }
|
|
15
|
+
},
|
|
16
|
+
body: { description: "Request body for POST/PUT/PATCH requests." }
|
|
17
|
+
},
|
|
18
|
+
required: ["method", "path"],
|
|
19
|
+
additionalProperties: false
|
|
20
|
+
};
|
|
21
|
+
function createRESTToolAdapter(config) {
|
|
22
|
+
const toolName = `rest_${config.name}`;
|
|
23
|
+
const buildTool = (spec) => {
|
|
24
|
+
const specStr = typeof spec === "string" ? spec : JSON.stringify(spec);
|
|
25
|
+
return {
|
|
26
|
+
name: toolName,
|
|
27
|
+
description: `Make a REST API call to the ${config.name} API. Based on the OpenAPI spec below, generate the appropriate request with method, path, query params, and body.
|
|
28
|
+
|
|
29
|
+
OpenAPI spec:
|
|
30
|
+
\`\`\`json
|
|
31
|
+
${specStr}
|
|
32
|
+
\`\`\``,
|
|
33
|
+
inputSchema: REST_INPUT_SCHEMA
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
const eager = typeof config.spec === "function" ? void 0 : [buildTool(config.spec)];
|
|
37
|
+
let pending;
|
|
38
|
+
return {
|
|
39
|
+
id: config.id ?? toolName,
|
|
40
|
+
getTools: () => {
|
|
41
|
+
if (eager) return eager;
|
|
42
|
+
if (!pending) {
|
|
43
|
+
const resolve = config.spec;
|
|
44
|
+
pending = Promise.resolve().then(resolve).then((spec) => [buildTool(spec)]);
|
|
45
|
+
pending.catch(() => {
|
|
46
|
+
pending = void 0;
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
return pending;
|
|
50
|
+
},
|
|
51
|
+
ownsTool: (name) => name === toolName,
|
|
52
|
+
execute: async (_name, args) => {
|
|
53
|
+
const request = args ?? {};
|
|
54
|
+
return config.execute({
|
|
55
|
+
method: request.method,
|
|
56
|
+
path: request.path,
|
|
57
|
+
query: request.query,
|
|
58
|
+
body: request.body
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
export {
|
|
64
|
+
createRESTToolAdapter
|
|
65
|
+
};
|
|
66
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/adapters.ts"],
|
|
4
|
+
"sourcesContent": ["import type { JSONSchema, RESTRequest, ToolAdapter, ToolDefinition } from \"@yak-io/javascript\";\n\n/** An OpenAPI spec as an object or a JSON string. */\ntype RESTSpec = Record<string, unknown> | string;\n\n/** Config for {@link createRESTToolAdapter}. */\nexport type RESTToolAdapterConfig = {\n /** Spec name \u2014 the tool is exposed to the LLM as `rest_<name>`. */\n name: string;\n /**\n * OpenAPI spec as an object or JSON string, or a resolver that returns it (sync or async). Use a\n * resolver to fetch the spec lazily: it runs once on first `getTools()` and the result is cached,\n * so the fetch is paid lazily, not at construction. A literal spec is embedded eagerly. Embedded\n * in the tool description so the LLM can author requests.\n */\n spec: RESTSpec | (() => RESTSpec | Promise<RESTSpec>);\n /**\n * Execute the LLM-authored REST request with YOUR client. Yak builds the tool and the request\n * shape (method / path / query / body) but never makes the call itself \u2014 your client owns the\n * base URL, auth, transport, and error handling. Use whatever you already have (a configured\n * `fetch` wrapper, axios, your app's API client, \u2026). Return the result the assistant should\n * see; throw to surface an error to the model.\n */\n execute: (request: RESTRequest) => unknown | Promise<unknown>;\n /** Stable id for diagnostics. Defaults to the tool name. */\n id?: string;\n};\n\n/**\n * Generic input schema for a REST tool. The LLM authors method/path/query/body;\n * the adapter hands them to your `execute` client. Do NOT inject `_reason` here \u2014 the\n * chat-ui / voice runtimes inject it when registering the tool with the model.\n */\nconst REST_INPUT_SCHEMA: JSONSchema = {\n type: \"object\",\n properties: {\n method: {\n type: \"string\",\n enum: [\"GET\", \"POST\", \"PUT\", \"PATCH\", \"DELETE\"],\n description: \"HTTP method for the request.\",\n },\n path: { type: \"string\", description: \"API path (e.g., '/users/123').\" },\n query: {\n type: \"object\",\n description: \"Query parameters as key-value pairs.\",\n additionalProperties: { type: \"string\" },\n },\n body: { description: \"Request body for POST/PUT/PATCH requests.\" },\n },\n required: [\"method\", \"path\"],\n additionalProperties: false,\n};\n\n/**\n * Create a REST {@link ToolAdapter}. Compose it into a {@link createYakToolset} so the generated\n * `rest_<name>` tool joins the single merged manifest + `onToolCall` funnel (and therefore\n * surfaces through `onToolCallComplete` / `useYakToolEvent`).\n *\n * The model authors a request from the OpenAPI spec you provide; the adapter hands it to your\n * `execute` callback, which runs it with whatever client you already use. Yak never constructs\n * the HTTP call \u2014 your client owns the base URL, auth, and transport.\n *\n * @example\n * ```ts\n * const billing = createRESTToolAdapter({\n * name: \"billing\",\n * spec: openapiSpec,\n * execute: (req) => myApiClient(req.method, req.path, { query: req.query, body: req.body }),\n * });\n * ```\n *\n * @example\n * ```ts\n * // Lazy: fetch the OpenAPI spec once, on first use. The factory stays synchronous \u2014 the fetch\n * // is paid when the toolset first materializes its tools, then cached.\n * const billing = createRESTToolAdapter({\n * name: \"billing\",\n * spec: async () => (await fetch(\"/openapi.json\")).json(),\n * execute: (req) => myApiClient(req.method, req.path, { query: req.query, body: req.body }),\n * });\n * ```\n */\nexport function createRESTToolAdapter(config: RESTToolAdapterConfig): ToolAdapter {\n const toolName = `rest_${config.name}`;\n\n const buildTool = (spec: RESTSpec): ToolDefinition => {\n // Minified, not pretty-printed: the model reads compact JSON identically, and the\n // spec is embedded verbatim in the tool description sent to the LLM on every turn \u2014\n // dropping the indentation/newlines is a free token saving for large specs.\n const specStr = typeof spec === \"string\" ? spec : JSON.stringify(spec);\n return {\n name: toolName,\n description:\n `Make a REST API call to the ${config.name} API. Based on the OpenAPI spec below, ` +\n \"generate the appropriate request with method, path, query params, and body.\" +\n `\\n\\nOpenAPI spec:\\n\\`\\`\\`json\\n${specStr}\\n\\`\\`\\``,\n inputSchema: REST_INPUT_SCHEMA,\n };\n };\n\n // A literal spec (object or string) builds the tool eagerly. A resolver runs once on first\n // getTools() and the [tool] result is cached; a rejection is NOT cached, so a later getTools()\n // can retry \u2014 a transient spec-fetch failure doesn't wedge the toolset.\n const eager = typeof config.spec === \"function\" ? undefined : [buildTool(config.spec)];\n let pending: Promise<ToolDefinition[]> | undefined;\n\n return {\n id: config.id ?? toolName,\n getTools: () => {\n if (eager) return eager;\n if (!pending) {\n const resolve = config.spec as () => RESTSpec | Promise<RESTSpec>;\n // Promise.resolve().then(resolve) so a synchronous throw also surfaces as a rejection.\n pending = Promise.resolve()\n .then(resolve)\n .then((spec) => [buildTool(spec)]);\n pending.catch(() => {\n pending = undefined; // don't cache failures \u2014 allow retry on the next getTools()\n });\n }\n return pending;\n },\n ownsTool: (name) => name === toolName,\n execute: async (_name, args) => {\n const request = (args ?? {}) as RESTRequest;\n // Hand a clean request to the caller's client \u2014 never forward the runtime-injected `_reason`.\n return config.execute({\n method: request.method,\n path: request.path,\n query: request.query,\n body: request.body,\n });\n },\n };\n}\n"],
|
|
5
|
+
"mappings": ";AAiCA,IAAM,oBAAgC;AAAA,EACpC,MAAM;AAAA,EACN,YAAY;AAAA,IACV,QAAQ;AAAA,MACN,MAAM;AAAA,MACN,MAAM,CAAC,OAAO,QAAQ,OAAO,SAAS,QAAQ;AAAA,MAC9C,aAAa;AAAA,IACf;AAAA,IACA,MAAM,EAAE,MAAM,UAAU,aAAa,iCAAiC;AAAA,IACtE,OAAO;AAAA,MACL,MAAM;AAAA,MACN,aAAa;AAAA,MACb,sBAAsB,EAAE,MAAM,SAAS;AAAA,IACzC;AAAA,IACA,MAAM,EAAE,aAAa,4CAA4C;AAAA,EACnE;AAAA,EACA,UAAU,CAAC,UAAU,MAAM;AAAA,EAC3B,sBAAsB;AACxB;AA+BO,SAAS,sBAAsB,QAA4C;AAChF,QAAM,WAAW,QAAQ,OAAO,IAAI;AAEpC,QAAM,YAAY,CAAC,SAAmC;AAIpD,UAAM,UAAU,OAAO,SAAS,WAAW,OAAO,KAAK,UAAU,IAAI;AACrE,WAAO;AAAA,MACL,MAAM;AAAA,MACN,aACE,+BAA+B,OAAO,IAAI;AAAA;AAAA;AAAA;AAAA,EAER,OAAO;AAAA;AAAA,MAC3C,aAAa;AAAA,IACf;AAAA,EACF;AAKA,QAAM,QAAQ,OAAO,OAAO,SAAS,aAAa,SAAY,CAAC,UAAU,OAAO,IAAI,CAAC;AACrF,MAAI;AAEJ,SAAO;AAAA,IACL,IAAI,OAAO,MAAM;AAAA,IACjB,UAAU,MAAM;AACd,UAAI,MAAO,QAAO;AAClB,UAAI,CAAC,SAAS;AACZ,cAAM,UAAU,OAAO;AAEvB,kBAAU,QAAQ,QAAQ,EACvB,KAAK,OAAO,EACZ,KAAK,CAAC,SAAS,CAAC,UAAU,IAAI,CAAC,CAAC;AACnC,gBAAQ,MAAM,MAAM;AAClB,oBAAU;AAAA,QACZ,CAAC;AAAA,MACH;AACA,aAAO;AAAA,IACT;AAAA,IACA,UAAU,CAAC,SAAS,SAAS;AAAA,IAC7B,SAAS,OAAO,OAAO,SAAS;AAC9B,YAAM,UAAW,QAAQ,CAAC;AAE1B,aAAO,OAAO,QAAQ;AAAA,QACpB,QAAQ,QAAQ;AAAA,QAChB,MAAM,QAAQ;AAAA,QACd,OAAO,QAAQ;AAAA,QACf,MAAM,QAAQ;AAAA,MAChB,CAAC;AAAA,IACH;AAAA,EACF;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yak-io/rest",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "REST/OpenAPI adapter for yak chatbot - exposes a REST API as a chatbot tool",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|
|
@@ -30,19 +30,20 @@
|
|
|
30
30
|
"LICENSE"
|
|
31
31
|
],
|
|
32
32
|
"sideEffects": false,
|
|
33
|
-
"main": "./dist/index.
|
|
33
|
+
"main": "./dist/index.cjs",
|
|
34
34
|
"module": "./dist/index.js",
|
|
35
35
|
"types": "./dist/index.d.ts",
|
|
36
36
|
"exports": {
|
|
37
37
|
".": {
|
|
38
38
|
"types": "./dist/index.d.ts",
|
|
39
39
|
"import": "./dist/index.js",
|
|
40
|
+
"require": "./dist/index.cjs",
|
|
40
41
|
"default": "./dist/index.js"
|
|
41
42
|
},
|
|
42
43
|
"./package.json": "./package.json"
|
|
43
44
|
},
|
|
44
45
|
"dependencies": {
|
|
45
|
-
"@yak-io/javascript": "0.
|
|
46
|
+
"@yak-io/javascript": "0.11.0"
|
|
46
47
|
},
|
|
47
48
|
"devDependencies": {
|
|
48
49
|
"@types/node": "^24.12.4",
|
|
@@ -51,7 +52,7 @@
|
|
|
51
52
|
},
|
|
52
53
|
"homepage": "https://docs.yak.io/docs/tool-adapters/rest",
|
|
53
54
|
"scripts": {
|
|
54
|
-
"build": "tsc",
|
|
55
|
+
"build": "node ../../scripts/build-package.mjs && tsc --emitDeclarationOnly",
|
|
55
56
|
"check-types": "tsc --noEmit",
|
|
56
57
|
"test": "vitest run",
|
|
57
58
|
"lint": "biome lint ./src --fix",
|
package/dist/adapters.js
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Generic input schema for a REST tool. The LLM authors method/path/query/body;
|
|
3
|
-
* the adapter hands them to your `execute` client. Do NOT inject `_reason` here — the
|
|
4
|
-
* chat-ui / voice runtimes inject it when registering the tool with the model.
|
|
5
|
-
*/
|
|
6
|
-
const REST_INPUT_SCHEMA = {
|
|
7
|
-
type: "object",
|
|
8
|
-
properties: {
|
|
9
|
-
method: {
|
|
10
|
-
type: "string",
|
|
11
|
-
enum: ["GET", "POST", "PUT", "PATCH", "DELETE"],
|
|
12
|
-
description: "HTTP method for the request.",
|
|
13
|
-
},
|
|
14
|
-
path: { type: "string", description: "API path (e.g., '/users/123')." },
|
|
15
|
-
query: {
|
|
16
|
-
type: "object",
|
|
17
|
-
description: "Query parameters as key-value pairs.",
|
|
18
|
-
additionalProperties: { type: "string" },
|
|
19
|
-
},
|
|
20
|
-
body: { description: "Request body for POST/PUT/PATCH requests." },
|
|
21
|
-
},
|
|
22
|
-
required: ["method", "path"],
|
|
23
|
-
additionalProperties: false,
|
|
24
|
-
};
|
|
25
|
-
/**
|
|
26
|
-
* Create a REST {@link ToolAdapter}. Compose it into a {@link createYakToolset} so the generated
|
|
27
|
-
* `rest_<name>` tool joins the single merged manifest + `onToolCall` funnel (and therefore
|
|
28
|
-
* surfaces through `onToolCallComplete` / `useYakToolEvent`).
|
|
29
|
-
*
|
|
30
|
-
* The model authors a request from the OpenAPI spec you provide; the adapter hands it to your
|
|
31
|
-
* `execute` callback, which runs it with whatever client you already use. Yak never constructs
|
|
32
|
-
* the HTTP call — your client owns the base URL, auth, and transport.
|
|
33
|
-
*
|
|
34
|
-
* @example
|
|
35
|
-
* ```ts
|
|
36
|
-
* const billing = createRESTToolAdapter({
|
|
37
|
-
* name: "billing",
|
|
38
|
-
* spec: openapiSpec,
|
|
39
|
-
* execute: (req) => myApiClient(req.method, req.path, { query: req.query, body: req.body }),
|
|
40
|
-
* });
|
|
41
|
-
* ```
|
|
42
|
-
*/
|
|
43
|
-
export function createRESTToolAdapter(config) {
|
|
44
|
-
const toolName = `rest_${config.name}`;
|
|
45
|
-
const specStr = typeof config.spec === "string" ? config.spec : JSON.stringify(config.spec, null, 2);
|
|
46
|
-
const tool = {
|
|
47
|
-
name: toolName,
|
|
48
|
-
description: `Make a REST API call to the ${config.name} API. Based on the OpenAPI spec below, ` +
|
|
49
|
-
"generate the appropriate request with method, path, query params, and body." +
|
|
50
|
-
`\n\nOpenAPI spec:\n\`\`\`json\n${specStr}\n\`\`\``,
|
|
51
|
-
inputSchema: REST_INPUT_SCHEMA,
|
|
52
|
-
};
|
|
53
|
-
return {
|
|
54
|
-
id: config.id ?? toolName,
|
|
55
|
-
getTools: () => [tool],
|
|
56
|
-
ownsTool: (name) => name === toolName,
|
|
57
|
-
execute: async (_name, args) => {
|
|
58
|
-
const request = (args ?? {});
|
|
59
|
-
// Hand a clean request to the caller's client — never forward the runtime-injected `_reason`.
|
|
60
|
-
return config.execute({
|
|
61
|
-
method: request.method,
|
|
62
|
-
path: request.path,
|
|
63
|
-
query: request.query,
|
|
64
|
-
body: request.body,
|
|
65
|
-
});
|
|
66
|
-
},
|
|
67
|
-
};
|
|
68
|
-
}
|