@cerebro-labs/agentnava-sdk 0.53.2 → 0.75.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 +3 -2
- package/agent.d.ts +57 -7
- package/agent.js +69 -9
- package/agent.js.map +1 -1
- package/attach.d.ts +3 -0
- package/attach.js.map +1 -1
- package/connection.d.ts +10 -0
- package/connection.js +13 -2
- package/connection.js.map +1 -1
- package/conversation.d.ts +37 -1
- package/conversation.js +28 -2
- package/conversation.js.map +1 -1
- package/end-user.d.ts +10 -0
- package/end-user.js +7 -0
- package/end-user.js.map +1 -1
- package/errors.d.ts +20 -0
- package/errors.js +24 -0
- package/errors.js.map +1 -1
- package/http.d.ts +4 -1
- package/http.js +21 -9
- package/http.js.map +1 -1
- package/import-tools.d.ts +5 -0
- package/import-tools.js +47 -6
- package/import-tools.js.map +1 -1
- package/index.d.ts +4 -3
- package/index.js +2 -1
- package/index.js.map +1 -1
- package/knowledge-base.d.ts +18 -18
- package/knowledge-base.js +28 -0
- package/knowledge-base.js.map +1 -1
- package/package.json +1 -1
- package/partner-app.d.ts +70 -0
- package/partner-app.js +125 -0
- package/partner-app.js.map +1 -0
- package/react.css +177 -28
- package/react.js +142 -22
- package/react.js.map +1 -1
- package/secret.d.ts +6 -0
- package/secret.js +15 -0
- package/secret.js.map +1 -1
- package/turn.js +6 -0
- package/turn.js.map +1 -1
- package/types.d.ts +144 -0
- package/types.js.map +1 -1
- package/version.d.ts +1 -1
- package/version.js +1 -1
- package/version.js.map +1 -1
- package/workspace.d.ts +35 -1
- package/workspace.js +35 -0
- package/workspace.js.map +1 -1
package/index.d.ts
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
export { AgentNava, Workspace } from './workspace.ts';
|
|
2
|
+
export { PartnerApp, type PartnerAppOptions, type ExchangedCode, type CreditGrant } from './partner-app.ts';
|
|
2
3
|
export { Agent, type StartOptions } from './agent.ts';
|
|
3
4
|
export { Account } from './account.ts';
|
|
4
5
|
export { Connection, type CreatorAccountConnection, type EndUserAccountConnection, } from './connection.ts';
|
|
5
6
|
export { App, type AppId } from './apps.generated.ts';
|
|
6
7
|
export { Secret } from './secret.ts';
|
|
7
8
|
export { EndUser } from './end-user.ts';
|
|
8
|
-
export { Conversation, type AskOptions, type FileUrlOptions } from './conversation.ts';
|
|
9
|
+
export { Conversation, type AskOptions, type AttachOptions, type FileUrlOptions } from './conversation.ts';
|
|
9
10
|
export { KnowledgeBase } from './knowledge-base.ts';
|
|
10
11
|
export { Database } from './database.ts';
|
|
11
12
|
export { Project, type ProjectStartOptions } from './project.ts';
|
|
12
|
-
export { AgentNavaError } from './errors.ts';
|
|
13
|
+
export { AgentNavaError, SecretHostNotAllowed } from './errors.ts';
|
|
13
14
|
export { loadWorkflows, parseWorkflow } from './workflows.ts';
|
|
14
15
|
export { importTools, fromOpenApi, fromOpenApiUrl, fromPostman, fromCurl, toolNameFrom, type ImportSource, type ImportOptions, type ImportedTools, type ImportedSecret, type ImportWarning, type ImportWarningKind, type OpenApiDocument, type PostmanCollection, } from './import-tools.ts';
|
|
15
16
|
export { loadAgentDir } from './load-agent-dir.ts';
|
|
16
17
|
export { loadManagerTemplate } from './templates.ts';
|
|
17
18
|
export type { Turn } from './turn.ts';
|
|
18
19
|
export type { ClientOptions } from './http.ts';
|
|
19
|
-
export type { AccountData, AccountNeeded, AccountStatus, AgentConnection, AgentConversationData, AgentData, AgentFields, AgentStatus, WebsiteChatAppearance, Attachment, Bindings, ConnectionStatus, Credits, Guardrails, DatabaseColumn, DatabaseData, DatabaseEngine, DatabaseFields, DatabaseLogin, DatabaseTable, DatabaseTestResult, DatabaseTls, DatabaseUsedBy, FileEntry, Filesystem, Grade, HttpTool, HttpToolParam, McpServer, ProjectAgent, Message, Parameter, ProjectConversationData, ProjectData, ProjectFields, Requirement, Scope, SeatPlan, SecretMeta, SecretRequirement, Step, Trigger, WebhookDelivery, WebhookVerify, TriggerTarget, TurnEvent, Widget, WidgetPart, UsedBy, WebsiteChat, WebsiteChatAccess, Workflow, WorkspaceInfo, WorkspaceMember, } from './types.ts';
|
|
20
|
+
export type { AppTool, AccountData, AccountNeeded, AccountStatus, AgentConnection, AgentConversationData, AgentData, AgentDraft, AgentFields, AgentStatus, WebsiteChatAppearance, Attachment, Bindings, ConnectionStatus, Credits, Guardrails, DatabaseColumn, DatabaseData, DatabaseEngine, DatabaseFields, DatabaseLogin, DatabaseTable, DatabaseTestResult, DatabaseTls, DatabaseUsedBy, FileEntry, Filesystem, Grade, HttpTool, HttpToolParam, McpServer, ProjectAgent, Message, Parameter, ProjectConversationData, ProjectData, ProjectFields, Requirement, Scope, SeatPlan, SecretMeta, SecretRequirement, Step, Trigger, WebhookDelivery, WebhookVerify, TriggerTarget, TurnEvent, Widget, WidgetPart, UsedBy, WebsiteChat, WebsiteChatAccess, Workflow, WorkspaceInfo, WorkspaceMember, } from './types.ts';
|
|
20
21
|
export { placeWidgets, type ReplyPiece } from './widgets.ts';
|
package/index.js
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
//
|
|
15
15
|
// The reference this implements is design/sdk-design.md.
|
|
16
16
|
export { AgentNava, Workspace } from "./workspace.js";
|
|
17
|
+
export { PartnerApp } from "./partner-app.js";
|
|
17
18
|
export { Agent } from "./agent.js";
|
|
18
19
|
export { Account } from "./account.js";
|
|
19
20
|
// Builds one entry of an agent's setup: an app, and whose account. NOT the
|
|
@@ -28,7 +29,7 @@ export { Conversation } from "./conversation.js";
|
|
|
28
29
|
export { KnowledgeBase } from "./knowledge-base.js";
|
|
29
30
|
export { Database } from "./database.js";
|
|
30
31
|
export { Project } from "./project.js";
|
|
31
|
-
export { AgentNavaError } from "./errors.js";
|
|
32
|
+
export { AgentNavaError, SecretHostNotAllowed } from "./errors.js";
|
|
32
33
|
export { loadWorkflows, parseWorkflow } from "./workflows.js";
|
|
33
34
|
// Draft an agent's tools from whatever a company already has: an OpenAPI
|
|
34
35
|
// document, a Postman collection, or a working curl command. Runs entirely on
|
package/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,8BAA8B;AAC9B,EAAE;AACF,0DAA0D;AAC1D,EAAE;AACF,6DAA6D;AAC7D,EAAE;AACF,yCAAyC;AACzC,2CAA2C;AAC3C,8BAA8B;AAC9B,8EAA8E;AAC9E,QAAQ;AACR,uEAAuE;AACvE,2EAA2E;AAC3E,EAAE;AACF,yDAAyD;AAEzD,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AACtD,OAAO,EAAE,KAAK,EAAqB,MAAM,YAAY,CAAC;AACtD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,2EAA2E;AAC3E,iFAAiF;AACjF,OAAO,EACN,UAAU,GAGV,MAAM,iBAAiB,CAAC;AACzB,6EAA6E;AAC7E,wEAAwE;AACxE,OAAO,EAAE,GAAG,EAAc,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxC,OAAO,EAAE,YAAY,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,8BAA8B;AAC9B,EAAE;AACF,0DAA0D;AAC1D,EAAE;AACF,6DAA6D;AAC7D,EAAE;AACF,yCAAyC;AACzC,2CAA2C;AAC3C,8BAA8B;AAC9B,8EAA8E;AAC9E,QAAQ;AACR,uEAAuE;AACvE,2EAA2E;AAC3E,EAAE;AACF,yDAAyD;AAEzD,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AACtD,OAAO,EAAE,UAAU,EAAgE,MAAM,kBAAkB,CAAC;AAC5G,OAAO,EAAE,KAAK,EAAqB,MAAM,YAAY,CAAC;AACtD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,2EAA2E;AAC3E,iFAAiF;AACjF,OAAO,EACN,UAAU,GAGV,MAAM,iBAAiB,CAAC;AACzB,6EAA6E;AAC7E,wEAAwE;AACxE,OAAO,EAAE,GAAG,EAAc,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxC,OAAO,EAAE,YAAY,EAA4D,MAAM,mBAAmB,CAAC;AAC3G,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,OAAO,EAA4B,MAAM,cAAc,CAAC;AACjE,OAAO,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC9D,yEAAyE;AACzE,8EAA8E;AAC9E,6EAA6E;AAC7E,2DAA2D;AAC3D,OAAO,EACN,WAAW,EACX,WAAW,EACX,cAAc,EACd,WAAW,EACX,QAAQ,EACR,YAAY,GASZ,MAAM,mBAAmB,CAAC;AAC3B,4EAA4E;AAC5E,kEAAkE;AAClE,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,+EAA+E;AAC/E,6EAA6E;AAC7E,2BAA2B;AAC3B,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AA6DrD,OAAO,EAAE,YAAY,EAAmB,MAAM,cAAc,CAAC","sourcesContent":["// @cerebro-labs/agentnava-sdk\n//\n// Configure an agent, start a conversation, work with it.\n//\n// import { AgentNava } from '@cerebro-labs/agentnava-sdk';\n//\n// const ws = AgentNava.getWorkspace();\n// const agent = await ws.agents.create({\n// name: 'Refund checker',\n// instructions: 'Decide whether a late delivery qualifies for a refund.',\n// });\n// const conversation = await agent.start({ endUserId: 'u_ananya' });\n// console.log(await conversation.ask('Order 4471 was nine days late.'));\n//\n// The reference this implements is design/sdk-design.md.\n\nexport { AgentNava, Workspace } from './workspace.ts';\nexport { PartnerApp, type PartnerAppOptions, type ExchangedCode, type CreditGrant } from './partner-app.ts';\nexport { Agent, type StartOptions } from './agent.ts';\nexport { Account } from './account.ts';\n// Builds one entry of an agent's setup: an app, and whose account. NOT the\n// 0.43 `Connection`, which was a handle on a signed-in account and is `Account`.\nexport {\n\tConnection,\n\ttype CreatorAccountConnection,\n\ttype EndUserAccountConnection,\n} from './connection.ts';\n// Every app the platform can connect, as constants: `App.GMAIL === 'gmail'`.\n// Generated from the connections catalog; any plain string still works.\nexport { App, type AppId } from './apps.generated.ts';\nexport { Secret } from './secret.ts';\nexport { EndUser } from './end-user.ts';\nexport { Conversation, type AskOptions, type AttachOptions, type FileUrlOptions } from './conversation.ts';\nexport { KnowledgeBase } from './knowledge-base.ts';\nexport { Database } from './database.ts';\nexport { Project, type ProjectStartOptions } from './project.ts';\nexport { AgentNavaError, SecretHostNotAllowed } from './errors.ts';\nexport { loadWorkflows, parseWorkflow } from './workflows.ts';\n// Draft an agent's tools from whatever a company already has: an OpenAPI\n// document, a Postman collection, or a working curl command. Runs entirely on\n// your machine: nothing about the source is sent to AgentNava, which matters\n// because those files routinely contain a live credential.\nexport {\n\timportTools,\n\tfromOpenApi,\n\tfromOpenApiUrl,\n\tfromPostman,\n\tfromCurl,\n\ttoolNameFrom,\n\ttype ImportSource,\n\ttype ImportOptions,\n\ttype ImportedTools,\n\ttype ImportedSecret,\n\ttype ImportWarning,\n\ttype ImportWarningKind,\n\ttype OpenApiDocument,\n\ttype PostmanCollection,\n} from './import-tools.ts';\n// Reads a whole Claude-Code-shaped agent directory (AGENT.md + workflows/ +\n// mcp-servers.json ...) into the fields ws.agents.create() takes.\nexport { loadAgentDir } from './load-agent-dir.ts';\n// The platform's built-in project-manager configuration. apps/api imports this\n// through the './templates' subpath; re-exported here so the package has one\n// surface rather than two.\nexport { loadManagerTemplate } from './templates.ts';\nexport type { Turn } from './turn.ts';\nexport type { ClientOptions } from './http.ts';\nexport type {\n\tAppTool,\n\tAccountData,\n\tAccountNeeded,\n\tAccountStatus,\n\tAgentConnection,\n\tAgentConversationData,\n\tAgentData,\n\tAgentDraft,\n\tAgentFields,\n\tAgentStatus,\n\tWebsiteChatAppearance,\n\tAttachment,\n\tBindings,\n\tConnectionStatus,\n\tCredits,\n\tGuardrails,\n\tDatabaseColumn,\n\tDatabaseData,\n\tDatabaseEngine,\n\tDatabaseFields,\n\tDatabaseLogin,\n\tDatabaseTable,\n\tDatabaseTestResult,\n\tDatabaseTls,\n\tDatabaseUsedBy,\n\tFileEntry,\n\tFilesystem,\n\tGrade,\n\tHttpTool,\n\tHttpToolParam,\n\tMcpServer,\n\tProjectAgent,\n\tMessage,\n\tParameter,\n\tProjectConversationData,\n\tProjectData,\n\tProjectFields,\n\tRequirement,\n\tScope,\n\tSeatPlan,\n\tSecretMeta,\n\tSecretRequirement,\n\tStep,\n\tTrigger,\n\tWebhookDelivery,\n\tWebhookVerify,\n\tTriggerTarget,\n\tTurnEvent,\n\tWidget,\n\tWidgetPart,\n\tUsedBy,\n\tWebsiteChat,\n\tWebsiteChatAccess,\n\tWorkflow,\n\tWorkspaceInfo,\n\tWorkspaceMember,\n} from './types.ts';\nexport { placeWidgets, type ReplyPiece } from './widgets.ts';\n"]}
|
package/knowledge-base.d.ts
CHANGED
|
@@ -1,23 +1,5 @@
|
|
|
1
1
|
import type { Http } from './http.ts';
|
|
2
2
|
import type { KnowledgeBaseData, KnowledgeFileEntry } from './types.ts';
|
|
3
|
-
/**
|
|
4
|
-
* A knowledge base: a named folder of files your agents read.
|
|
5
|
-
*
|
|
6
|
-
* A base lives on the WORKSPACE, not on one agent, and an agent configuration
|
|
7
|
-
* attaches it by key. Two agents attaching the same base is the point: correct a
|
|
8
|
-
* file once and both see the correction.
|
|
9
|
-
*
|
|
10
|
-
* Attached bases are mounted READ-ONLY at `/knowledge/<key>/` when a session
|
|
11
|
-
* starts, so the agent finds things the way a person would, with `ls`, `grep`
|
|
12
|
-
* and `cat`. There is no search endpoint and no embedding step, deliberately;
|
|
13
|
-
* see the NO EMBEDDINGS decision in CLAUDE.md.
|
|
14
|
-
*
|
|
15
|
-
* ```ts
|
|
16
|
-
* const kb = await ws.knowledgeBases.create({ key: 'product-docs', name: 'Docs' });
|
|
17
|
-
* await kb.files.put('/pricing/tiers.md', bytes, 'text/markdown');
|
|
18
|
-
* await ws.agents.create({ name: 'Nava', knowledge: ['product-docs'] });
|
|
19
|
-
* ```
|
|
20
|
-
*/
|
|
21
3
|
export declare class KnowledgeBase {
|
|
22
4
|
#private;
|
|
23
5
|
/** Workspace-unique, and what an agent configuration stores. Not editable:
|
|
@@ -106,6 +88,24 @@ export declare class KnowledgeBase {
|
|
|
106
88
|
* finish.
|
|
107
89
|
*/
|
|
108
90
|
retry: (path: string) => Promise<void>;
|
|
91
|
+
/**
|
|
92
|
+
* Make the base hold exactly these files: write each one, then remove
|
|
93
|
+
* every file not among them.
|
|
94
|
+
*
|
|
95
|
+
* For an app that keeps a base in step with a source it reads again (a
|
|
96
|
+
* website, a store): a page the source dropped leaves the base too.
|
|
97
|
+
* Paths are compared the way the API stores them, with a leading slash,
|
|
98
|
+
* so `pages/home.md` and `/pages/home.md` are the same file. Comparing
|
|
99
|
+
* them as written once removed every file a sync had just written.
|
|
100
|
+
*/
|
|
101
|
+
mirror: (files: {
|
|
102
|
+
path: string;
|
|
103
|
+
text: string;
|
|
104
|
+
contentType?: string;
|
|
105
|
+
}[]) => Promise<{
|
|
106
|
+
written: number;
|
|
107
|
+
removed: number;
|
|
108
|
+
}>;
|
|
109
109
|
/**
|
|
110
110
|
* Upload a whole directory, preserving its structure under `prefix`.
|
|
111
111
|
*
|
package/knowledge-base.js
CHANGED
|
@@ -16,6 +16,10 @@
|
|
|
16
16
|
* await ws.agents.create({ name: 'Nava', knowledge: ['product-docs'] });
|
|
17
17
|
* ```
|
|
18
18
|
*/
|
|
19
|
+
/** A file's path as the API stores it: always with one leading slash. */
|
|
20
|
+
function storedPath(path) {
|
|
21
|
+
return `/${path.replace(/^\/+/, '')}`;
|
|
22
|
+
}
|
|
19
23
|
export class KnowledgeBase {
|
|
20
24
|
/** Workspace-unique, and what an agent configuration stores. Not editable:
|
|
21
25
|
* renaming it would move every attachment silently. */
|
|
@@ -150,6 +154,30 @@ export class KnowledgeBase {
|
|
|
150
154
|
retry: async (path) => {
|
|
151
155
|
await this.#http.post(`${this.base}/files/retry`, { path });
|
|
152
156
|
},
|
|
157
|
+
/**
|
|
158
|
+
* Make the base hold exactly these files: write each one, then remove
|
|
159
|
+
* every file not among them.
|
|
160
|
+
*
|
|
161
|
+
* For an app that keeps a base in step with a source it reads again (a
|
|
162
|
+
* website, a store): a page the source dropped leaves the base too.
|
|
163
|
+
* Paths are compared the way the API stores them, with a leading slash,
|
|
164
|
+
* so `pages/home.md` and `/pages/home.md` are the same file. Comparing
|
|
165
|
+
* them as written once removed every file a sync had just written.
|
|
166
|
+
*/
|
|
167
|
+
mirror: async (files) => {
|
|
168
|
+
const wanted = new Set(files.map((f) => storedPath(f.path)));
|
|
169
|
+
for (const file of files) {
|
|
170
|
+
await this.files.put(file.path, new TextEncoder().encode(file.text), file.contentType ?? 'text/markdown');
|
|
171
|
+
}
|
|
172
|
+
let removed = 0;
|
|
173
|
+
for (const entry of await this.files.list()) {
|
|
174
|
+
if (wanted.has(storedPath(entry.path)))
|
|
175
|
+
continue;
|
|
176
|
+
await this.files.remove(entry.path);
|
|
177
|
+
removed++;
|
|
178
|
+
}
|
|
179
|
+
return { written: files.length, removed };
|
|
180
|
+
},
|
|
153
181
|
/**
|
|
154
182
|
* Upload a whole directory, preserving its structure under `prefix`.
|
|
155
183
|
*
|
package/knowledge-base.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"knowledge-base.js","sourceRoot":"","sources":["../src/knowledge-base.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Http } from './http.ts';\nimport type { KnowledgeBaseData, KnowledgeFileEntry } from './types.ts';\n\n/**\n * A knowledge base: a named folder of files your agents read.\n *\n * A base lives on the WORKSPACE, not on one agent, and an agent configuration\n * attaches it by key. Two agents attaching the same base is the point: correct a\n * file once and both see the correction.\n *\n * Attached bases are mounted READ-ONLY at `/knowledge/<key>/` when a session\n * starts, so the agent finds things the way a person would, with `ls`, `grep`\n * and `cat`. There is no search endpoint and no embedding step, deliberately;\n * see the NO EMBEDDINGS decision in CLAUDE.md.\n *\n * ```ts\n * const kb = await ws.knowledgeBases.create({ key: 'product-docs', name: 'Docs' });\n * await kb.files.put('/pricing/tiers.md', bytes, 'text/markdown');\n * await ws.agents.create({ name: 'Nava', knowledge: ['product-docs'] });\n * ```\n */\nexport class KnowledgeBase {\n\t/** Workspace-unique, and what an agent configuration stores. Not editable:\n\t * renaming it would move every attachment silently. */\n\treadonly key: string;\n\n\treadonly #http: Http;\n\tprivate snapshot?: KnowledgeBaseData;\n\n\tconstructor(http: Http, key: string, snapshot?: KnowledgeBaseData) {\n\t\tthis.#http = http;\n\t\tthis.key = key;\n\t\tif (snapshot) this.snapshot = snapshot;\n\t}\n\n\tprivate get base(): string {\n\t\treturn `/v1/knowledge/bases/${encodeURIComponent(this.key)}`;\n\t}\n\n\t/** The base's current record: name, description, file count, timestamps. */\n\tasync get(): Promise<KnowledgeBaseData> {\n\t\tconst res = await this.#http.get<{ base: KnowledgeBaseData }>(this.base);\n\t\tthis.snapshot = res.base;\n\t\treturn res.base;\n\t}\n\n\t/** What was known at the time this handle was made, without a round trip.\n\t * Undefined on a handle built from a bare key. */\n\tget data(): KnowledgeBaseData | undefined {\n\t\treturn this.snapshot;\n\t}\n\n\t/** Change the display name or description. The KEY is deliberately not\n\t * changeable here: agent configurations point at it. */\n\tasync update(fields: { name?: string; description?: string }): Promise<KnowledgeBase> {\n\t\tconst res = await this.#http.patch<{ base: KnowledgeBaseData }>(this.base, fields);\n\t\tthis.snapshot = res.base;\n\t\treturn new KnowledgeBase(this.#http, this.key, res.base);\n\t}\n\n\t/**\n\t * Delete the base and everything in it.\n\t *\n\t * REFUSES while any agent still attaches it, with an error naming them. That\n\t * refusal is what makes attaching safe: an agent configuration that named a\n\t * base at save time would otherwise find it missing the next morning. Call\n\t * `attachments()` first if you want to know before you try.\n\t */\n\tasync delete(): Promise<void> {\n\t\tawait this.#http.delete(this.base);\n\t}\n\n\t/**\n\t * Which agents read this base.\n\t *\n\t * The question worth asking before deleting or reorganising one: who breaks?\n\t * Read from each agent's current configuration, so it reflects what a session\n\t * would actually mount rather than what somebody typed into a draft.\n\t */\n\tasync attachments(): Promise<{ agentId: string; agentKey?: string; name?: string }[]> {\n\t\tconst res = await this.#http.get<{\n\t\t\tagents: { agentId: string; agentKey?: string; name?: string }[];\n\t\t}>(`${this.base}/attachments`);\n\t\treturn res.agents ?? [];\n\t}\n\n\t/**\n\t * The files in this base.\n\t *\n\t * Paths, not flat names, so `/pricing/tiers.md` is a file inside `pricing/`\n\t * and the session mount reproduces that shape exactly.\n\t */\n\treadonly files = {\n\t\t/**\n\t\t * Upload, or REPLACE what is already at this path. A path holds one file;\n\t\t * re-uploading it is a correction, not a second copy.\n\t\t */\n\t\tput: async (\n\t\t\tpath: string,\n\t\t\tbytes: Uint8Array | ArrayBuffer,\n\t\t\tcontentType?: string,\n\t\t): Promise<KnowledgeFileEntry> => {\n\t\t\tconst res = await this.#http.request<{ file: KnowledgeFileEntry }>(`${this.base}/files`, {\n\t\t\t\tmethod: 'PUT',\n\t\t\t\tbytes,\n\t\t\t\tquery: { path },\n\t\t\t\t...(contentType && { contentType }),\n\t\t\t});\n\t\t\treturn res.file;\n\t\t},\n\n\t\t/** List files, optionally under one prefix. Ordered by path, so a listing\n\t\t * reads like a directory rather than an upload log. */\n\t\tlist: async (prefix = '/'): Promise<KnowledgeFileEntry[]> => {\n\t\t\tconst res = await this.#http.get<{ files: KnowledgeFileEntry[] }>(`${this.base}/files`, {\n\t\t\t\t...(prefix && prefix !== '/' ? { prefix } : {}),\n\t\t\t});\n\t\t\treturn res.files ?? [];\n\t\t},\n\n\t\t/**\n\t\t * Read a file's bytes back.\n\t\t *\n\t\t * Exists because until now you could write a file and list it but never\n\t\t * read it, so a script had no way to verify its own upload except by\n\t\t * asking an agent.\n\t\t */\n\t\tget: async (path: string): Promise<Uint8Array> => {\n\t\t\t// `send`, not `request`. `request` ALWAYS parses the body as JSON; the\n\t\t\t// `raw` flag it used to be passed was declared in the options type and\n\t\t\t// read by nothing, so this returned a parsed object (or threw \"Expected\n\t\t\t// JSON\") and then called `.arrayBuffer()` on it. Every call failed.\n\t\t\tconst res = await this.#http.send(`${this.base}/files/content`, { query: { path } });\n\t\t\treturn new Uint8Array(await res.arrayBuffer());\n\t\t},\n\n\t\t/**\n\t\t * Read a file back as a string.\n\t\t *\n\t\t * `get` returns bytes, which is right for a PDF and awkward for text: you\n\t\t * decode, change something, and encode again. This is the same file\n\t\t * already decoded, for the case people actually hit, which is reading a\n\t\t * document to check it or to correct it.\n\t\t */\n\t\ttext: async (path: string): Promise<string> => {\n\t\t\t// Same fix as `get` above, and this one failed in a nastier way: for a\n\t\t\t// JSON file `request` PARSED it and returned an object, so the failure\n\t\t\t// was `.text is not a function`; for markdown it threw \"Expected JSON\n\t\t\t// from ...\". Two different errors, one cause, neither mentioning it.\n\t\t\tconst res = await this.#http.send(`${this.base}/files/content`, { query: { path } });\n\t\t\treturn res.text();\n\t\t},\n\n\t\tremove: async (path: string): Promise<void> => {\n\t\t\tawait this.#http.delete(`${this.base}/files`, { path });\n\t\t},\n\n\t\t/**\n\t\t * Prepare a file again.\n\t\t *\n\t\t * Preparation happens by itself when a file is uploaded, so this is for\n\t\t * the times it did not work: a scan that came out blank, or a document\n\t\t * that failed before we could read its format and can now.\n\t\t *\n\t\t * It also hands back a file you have edited by hand. Anything you write\n\t\t * yourself is left alone by later runs, and calling this returns it to\n\t\t * being generated.\n\t\t *\n\t\t * Returns immediately. Watch `status` on the file listing to see it\n\t\t * finish.\n\t\t */\n\t\tretry: async (path: string): Promise<void> => {\n\t\t\tawait this.#http.post(`${this.base}/files/retry`, { path });\n\t\t},\n\n\t\t/**\n\t\t * Upload a whole directory, preserving its structure under `prefix`.\n\t\t *\n\t\t * Sugar over repeated `put`, but it is the operation people actually\n\t\t * perform, and hand-rolling the walk is where mistakes about path prefixes\n\t\t * get made. Node and Bun only: it reads the local filesystem.\n\t\t *\n\t\t * Sequential on purpose. A knowledge base is uploaded once in a while, not\n\t\t * in a hot path, and a burst of parallel writes against one base is a good\n\t\t * way to meet a rate limit for no gain.\n\t\t */\n\t\tputDirectory: async (\n\t\t\tdir: string,\n\t\t\toptions: { prefix?: string; include?: string[] } = {},\n\t\t): Promise<KnowledgeFileEntry[]> => {\n\t\t\tconst { readdir, readFile } = await import('node:fs/promises');\n\t\t\tconst { join, relative, posix, sep } = await import('node:path');\n\n\t\t\tconst prefix = (options.prefix ?? '/').replace(/\\/+$/, '');\n\t\t\tconst matches = (rel: string): boolean => {\n\t\t\t\tif (!options.include || options.include.length === 0) return true;\n\t\t\t\treturn options.include.some((pattern) => globMatches(pattern, rel));\n\t\t\t};\n\n\t\t\tconst walk = async (current: string): Promise<string[]> => {\n\t\t\t\tconst entries = await readdir(current, { withFileTypes: true });\n\t\t\t\tconst out: string[] = [];\n\t\t\t\tfor (const e of entries) {\n\t\t\t\t\tconst full = join(current, e.name);\n\t\t\t\t\tif (e.isDirectory()) out.push(...(await walk(full)));\n\t\t\t\t\telse if (e.isFile()) out.push(full);\n\t\t\t\t}\n\t\t\t\treturn out;\n\t\t\t};\n\n\t\t\tconst uploaded: KnowledgeFileEntry[] = [];\n\t\t\tfor (const full of await walk(dir)) {\n\t\t\t\tconst rel = relative(dir, full).split(sep).join(posix.sep);\n\t\t\t\tif (!matches(rel)) continue;\n\t\t\t\tconst bytes = await readFile(full);\n\t\t\t\tuploaded.push(\n\t\t\t\t\tawait this.files.put(\n\t\t\t\t\t\t`${prefix}/${rel}`,\n\t\t\t\t\t\tnew Uint8Array(bytes),\n\t\t\t\t\t\tcontentTypeFor(rel),\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn uploaded;\n\t\t},\n\t};\n\n\t/**\n\t * The agent that looks after this base.\n\t *\n\t * Every base has one. It reads the documents you upload, turns the ones an\n\t * agent cannot open into text, and keeps a list of what is in here. That\n\t * happens by itself; these methods are for asking it to do something\n\t * specific.\n\t *\n\t * It lives in your own workspace, so its runs cost turns like any other\n\t * agent's, and you can see what it did.\n\t */\n\treadonly librarian = {\n\t\t/**\n\t\t * Work through the whole base: read what has not been read, and rewrite\n\t\t * the list of what is in here.\n\t\t *\n\t\t * The same routine every time, which is the difference between this and\n\t\t * `ask`. Reach for it after uploading a lot at once, or when something\n\t\t * looks out of date.\n\t\t *\n\t\t * RETURNS IMMEDIATELY. The work is queued and runs within about a minute,\n\t\t * because reading a base can take several minutes and no HTTP call should\n\t\t * be held open that long. Watch `status` on the file listing to see it\n\t\t * finish, exactly as with `files.retry`.\n\t\t */\n\t\torganize: async (): Promise<void> => {\n\t\t\tawait this.#http.post(`${this.base}/librarian/organize`, {});\n\t\t},\n\n\t\t/**\n\t\t * Ask the librarian to do something to this base.\n\t\t *\n\t\t * 'split the handbook by chapter'\n\t\t * 'the text from that scan came out garbled, read it again'\n\t\t * 'what is in here about pricing?'\n\t\t *\n\t\t * It can add and change files, which is its job. Your documents are not\n\t\t * sacred to it, so ask it for what you want rather than working around it.\n\t\t */\n\t\task: async (text: string): Promise<{ reply: string }> => {\n\t\t\treturn this.#http.post<{ reply: string }>(`${this.base}/librarian/ask`, { text });\n\t\t},\n\t};\n}\n\n/**\n * Enough glob for `**\\/*.md` and `docs/*.txt`, which is what `include` is for.\n *\n * Deliberately not a glob library. Pulling a dependency into a published SDK so\n * that one convenience method can match filenames is a poor trade, and the\n * patterns people pass here are simple. Anything more complex: list the files\n * yourself and call `put`.\n */\nfunction globMatches(pattern: string, path: string): boolean {\n\tconst rx = pattern\n\t\t.split('/')\n\t\t.map((seg) => {\n\t\t\tif (seg === '**') return '.*';\n\t\t\treturn seg.replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&').replace(/\\*/g, '[^/]*');\n\t\t})\n\t\t.join('/')\n\t\t// `**/` should also match zero directories, so `**\\/*.md` finds `a.md`.\n\t\t.replace(/\\.\\*\\//g, '(?:.*/)?');\n\treturn new RegExp(`^${rx}$`).test(path);\n}\n\n/** A content type from the extension, so uploads are not all octet-stream. */\nfunction contentTypeFor(path: string): string {\n\tconst ext = path.slice(path.lastIndexOf('.') + 1).toLowerCase();\n\tconst known: Record<string, string> = {\n\t\tmd: 'text/markdown',\n\t\tmarkdown: 'text/markdown',\n\t\ttxt: 'text/plain',\n\t\tcsv: 'text/csv',\n\t\tjson: 'application/json',\n\t\thtml: 'text/html',\n\t\tpdf: 'application/pdf',\n\t};\n\treturn known[ext] ?? 'application/octet-stream';\n}\n"]}
|
|
1
|
+
{"version":3,"file":"knowledge-base.js","sourceRoot":"","sources":["../src/knowledge-base.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;GAiBG;AACH,yEAAyE;AACzE,SAAS,UAAU,CAAC,IAAY;IAC/B,OAAO,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,EAAE,CAAC;AACvC,CAAC;AAED,MAAM,OAAO,aAAa;IACzB;4DACwD;IAC/C,GAAG,CAAS;IAEZ,KAAK,CAAO;IACb,QAAQ,CAAqB;IAErC,YAAY,IAAU,EAAE,GAAW,EAAE,QAA4B;QAChE,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QAClB,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;QACf,IAAI,QAAQ;YAAE,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IACxC,CAAC;IAED,IAAY,IAAI;QACf,OAAO,uBAAuB,kBAAkB,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;IAC9D,CAAC;IAED,4EAA4E;IAC5E,KAAK,CAAC,GAAG;QACR,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAA8B,IAAI,CAAC,IAAI,CAAC,CAAC;QACzE,IAAI,CAAC,QAAQ,GAAG,GAAG,CAAC,IAAI,CAAC;QACzB,OAAO,GAAG,CAAC,IAAI,CAAC;IACjB,CAAC;IAED;uDACmD;IACnD,IAAI,IAAI;QACP,OAAO,IAAI,CAAC,QAAQ,CAAC;IACtB,CAAC;IAED;6DACyD;IACzD,KAAK,CAAC,MAAM,CAAC,MAA+C;QAC3D,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,KAAK,CAA8B,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACnF,IAAI,CAAC,QAAQ,GAAG,GAAG,CAAC,IAAI,CAAC;QACzB,OAAO,IAAI,aAAa,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC;IAC1D,CAAC;IAED;;;;;;;OAOG;IACH,KAAK,CAAC,MAAM;QACX,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACpC,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,WAAW;QAChB,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAE7B,GAAG,IAAI,CAAC,IAAI,cAAc,CAAC,CAAC;QAC/B,OAAO,GAAG,CAAC,MAAM,IAAI,EAAE,CAAC;IACzB,CAAC;IAED;;;;;OAKG;IACM,KAAK,GAAG;QAChB;;;WAGG;QACH,GAAG,EAAE,KAAK,EACT,IAAY,EACZ,KAA+B,EAC/B,WAAoB,EACU,EAAE;YAChC,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,CAA+B,GAAG,IAAI,CAAC,IAAI,QAAQ,EAAE;gBACxF,MAAM,EAAE,KAAK;gBACb,KAAK;gBACL,KAAK,EAAE,EAAE,IAAI,EAAE;gBACf,GAAG,CAAC,WAAW,IAAI,EAAE,WAAW,EAAE,CAAC;aACnC,CAAC,CAAC;YACH,OAAO,GAAG,CAAC,IAAI,CAAC;QACjB,CAAC;QAED;gEACwD;QACxD,IAAI,EAAE,KAAK,EAAE,MAAM,GAAG,GAAG,EAAiC,EAAE;YAC3D,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAkC,GAAG,IAAI,CAAC,IAAI,QAAQ,EAAE;gBACvF,GAAG,CAAC,MAAM,IAAI,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aAC/C,CAAC,CAAC;YACH,OAAO,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC;QACxB,CAAC;QAED;;;;;;WAMG;QACH,GAAG,EAAE,KAAK,EAAE,IAAY,EAAuB,EAAE;YAChD,uEAAuE;YACvE,uEAAuE;YACvE,wEAAwE;YACxE,oEAAoE;YACpE,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,IAAI,gBAAgB,EAAE,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC;YACrF,OAAO,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,WAAW,EAAE,CAAC,CAAC;QAChD,CAAC;QAED;;;;;;;WAOG;QACH,IAAI,EAAE,KAAK,EAAE,IAAY,EAAmB,EAAE;YAC7C,uEAAuE;YACvE,uEAAuE;YACvE,sEAAsE;YACtE,qEAAqE;YACrE,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,IAAI,gBAAgB,EAAE,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC;YACrF,OAAO,GAAG,CAAC,IAAI,EAAE,CAAC;QACnB,CAAC;QAED,MAAM,EAAE,KAAK,EAAE,IAAY,EAAiB,EAAE;YAC7C,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,QAAQ,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;QACzD,CAAC;QAED;;;;;;;;;;;;;WAaG;QACH,KAAK,EAAE,KAAK,EAAE,IAAY,EAAiB,EAAE;YAC5C,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,IAAI,cAAc,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;QAC7D,CAAC;QAED;;;;;;;;;WASG;QACH,MAAM,EAAE,KAAK,EACZ,KAA6D,EACb,EAAE;YAClD,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAC7D,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;gBAC1B,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,WAAW,IAAI,eAAe,CAAC,CAAC;YAC3G,CAAC;YACD,IAAI,OAAO,GAAG,CAAC,CAAC;YAChB,KAAK,MAAM,KAAK,IAAI,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC;gBAC7C,IAAI,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;oBAAE,SAAS;gBACjD,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;gBACpC,OAAO,EAAE,CAAC;YACX,CAAC;YACD,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC;QAC3C,CAAC;QAED;;;;;;;;;;WAUG;QACH,YAAY,EAAE,KAAK,EAClB,GAAW,EACX,UAAmD,EAAE,EACrB,EAAE;YAClC,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,MAAM,MAAM,CAAC,kBAAkB,CAAC,CAAC;YAC/D,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,CAAC;YAEjE,MAAM,MAAM,GAAG,CAAC,OAAO,CAAC,MAAM,IAAI,GAAG,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;YAC3D,MAAM,OAAO,GAAG,CAAC,GAAW,EAAW,EAAE;gBACxC,IAAI,CAAC,OAAO,CAAC,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC;oBAAE,OAAO,IAAI,CAAC;gBAClE,OAAO,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,WAAW,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC;YACrE,CAAC,CAAC;YAEF,MAAM,IAAI,GAAG,KAAK,EAAE,OAAe,EAAqB,EAAE;gBACzD,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,OAAO,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;gBAChE,MAAM,GAAG,GAAa,EAAE,CAAC;gBACzB,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;oBACzB,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;oBACnC,IAAI,CAAC,CAAC,WAAW,EAAE;wBAAE,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;yBAChD,IAAI,CAAC,CAAC,MAAM,EAAE;wBAAE,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBACrC,CAAC;gBACD,OAAO,GAAG,CAAC;YACZ,CAAC,CAAC;YAEF,MAAM,QAAQ,GAAyB,EAAE,CAAC;YAC1C,KAAK,MAAM,IAAI,IAAI,MAAM,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;gBACpC,MAAM,GAAG,GAAG,QAAQ,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;gBAC3D,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC;oBAAE,SAAS;gBAC5B,MAAM,KAAK,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,CAAC;gBACnC,QAAQ,CAAC,IAAI,CACZ,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CACnB,GAAG,MAAM,IAAI,GAAG,EAAE,EAClB,IAAI,UAAU,CAAC,KAAK,CAAC,EACrB,cAAc,CAAC,GAAG,CAAC,CACnB,CACD,CAAC;YACH,CAAC;YACD,OAAO,QAAQ,CAAC;QACjB,CAAC;KACD,CAAC;IAEF;;;;;;;;;;OAUG;IACM,SAAS,GAAG;QACpB;;;;;;;;;;;;WAYG;QACH,QAAQ,EAAE,KAAK,IAAmB,EAAE;YACnC,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,IAAI,qBAAqB,EAAE,EAAE,CAAC,CAAC;QAC9D,CAAC;QAED;;;;;;;;;WASG;QACH,GAAG,EAAE,KAAK,EAAE,IAAY,EAA8B,EAAE;YACvD,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAoB,GAAG,IAAI,CAAC,IAAI,gBAAgB,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;QACnF,CAAC;KACD,CAAC;CACF;AAED;;;;;;;GAOG;AACH,SAAS,WAAW,CAAC,OAAe,EAAE,IAAY;IACjD,MAAM,EAAE,GAAG,OAAO;SAChB,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;QACZ,IAAI,GAAG,KAAK,IAAI;YAAE,OAAO,IAAI,CAAC;QAC9B,OAAO,GAAG,CAAC,OAAO,CAAC,mBAAmB,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IACzE,CAAC,CAAC;SACD,IAAI,CAAC,GAAG,CAAC;QACV,wEAAwE;SACvE,OAAO,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC;IACjC,OAAO,IAAI,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACzC,CAAC;AAED,8EAA8E;AAC9E,SAAS,cAAc,CAAC,IAAY;IACnC,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;IAChE,MAAM,KAAK,GAA2B;QACrC,EAAE,EAAE,eAAe;QACnB,QAAQ,EAAE,eAAe;QACzB,GAAG,EAAE,YAAY;QACjB,GAAG,EAAE,UAAU;QACf,IAAI,EAAE,kBAAkB;QACxB,IAAI,EAAE,WAAW;QACjB,GAAG,EAAE,iBAAiB;KACtB,CAAC;IACF,OAAO,KAAK,CAAC,GAAG,CAAC,IAAI,0BAA0B,CAAC;AACjD,CAAC","sourcesContent":["import type { Http } from './http.ts';\nimport type { KnowledgeBaseData, KnowledgeFileEntry } from './types.ts';\n\n/**\n * A knowledge base: a named folder of files your agents read.\n *\n * A base lives on the WORKSPACE, not on one agent, and an agent configuration\n * attaches it by key. Two agents attaching the same base is the point: correct a\n * file once and both see the correction.\n *\n * Attached bases are mounted READ-ONLY at `/knowledge/<key>/` when a session\n * starts, so the agent finds things the way a person would, with `ls`, `grep`\n * and `cat`. There is no search endpoint and no embedding step, deliberately;\n * see the NO EMBEDDINGS decision in CLAUDE.md.\n *\n * ```ts\n * const kb = await ws.knowledgeBases.create({ key: 'product-docs', name: 'Docs' });\n * await kb.files.put('/pricing/tiers.md', bytes, 'text/markdown');\n * await ws.agents.create({ name: 'Nava', knowledge: ['product-docs'] });\n * ```\n */\n/** A file's path as the API stores it: always with one leading slash. */\nfunction storedPath(path: string): string {\n\treturn `/${path.replace(/^\\/+/, '')}`;\n}\n\nexport class KnowledgeBase {\n\t/** Workspace-unique, and what an agent configuration stores. Not editable:\n\t * renaming it would move every attachment silently. */\n\treadonly key: string;\n\n\treadonly #http: Http;\n\tprivate snapshot?: KnowledgeBaseData;\n\n\tconstructor(http: Http, key: string, snapshot?: KnowledgeBaseData) {\n\t\tthis.#http = http;\n\t\tthis.key = key;\n\t\tif (snapshot) this.snapshot = snapshot;\n\t}\n\n\tprivate get base(): string {\n\t\treturn `/v1/knowledge/bases/${encodeURIComponent(this.key)}`;\n\t}\n\n\t/** The base's current record: name, description, file count, timestamps. */\n\tasync get(): Promise<KnowledgeBaseData> {\n\t\tconst res = await this.#http.get<{ base: KnowledgeBaseData }>(this.base);\n\t\tthis.snapshot = res.base;\n\t\treturn res.base;\n\t}\n\n\t/** What was known at the time this handle was made, without a round trip.\n\t * Undefined on a handle built from a bare key. */\n\tget data(): KnowledgeBaseData | undefined {\n\t\treturn this.snapshot;\n\t}\n\n\t/** Change the display name or description. The KEY is deliberately not\n\t * changeable here: agent configurations point at it. */\n\tasync update(fields: { name?: string; description?: string }): Promise<KnowledgeBase> {\n\t\tconst res = await this.#http.patch<{ base: KnowledgeBaseData }>(this.base, fields);\n\t\tthis.snapshot = res.base;\n\t\treturn new KnowledgeBase(this.#http, this.key, res.base);\n\t}\n\n\t/**\n\t * Delete the base and everything in it.\n\t *\n\t * REFUSES while any agent still attaches it, with an error naming them. That\n\t * refusal is what makes attaching safe: an agent configuration that named a\n\t * base at save time would otherwise find it missing the next morning. Call\n\t * `attachments()` first if you want to know before you try.\n\t */\n\tasync delete(): Promise<void> {\n\t\tawait this.#http.delete(this.base);\n\t}\n\n\t/**\n\t * Which agents read this base.\n\t *\n\t * The question worth asking before deleting or reorganising one: who breaks?\n\t * Read from each agent's current configuration, so it reflects what a session\n\t * would actually mount rather than what somebody typed into a draft.\n\t */\n\tasync attachments(): Promise<{ agentId: string; agentKey?: string; name?: string }[]> {\n\t\tconst res = await this.#http.get<{\n\t\t\tagents: { agentId: string; agentKey?: string; name?: string }[];\n\t\t}>(`${this.base}/attachments`);\n\t\treturn res.agents ?? [];\n\t}\n\n\t/**\n\t * The files in this base.\n\t *\n\t * Paths, not flat names, so `/pricing/tiers.md` is a file inside `pricing/`\n\t * and the session mount reproduces that shape exactly.\n\t */\n\treadonly files = {\n\t\t/**\n\t\t * Upload, or REPLACE what is already at this path. A path holds one file;\n\t\t * re-uploading it is a correction, not a second copy.\n\t\t */\n\t\tput: async (\n\t\t\tpath: string,\n\t\t\tbytes: Uint8Array | ArrayBuffer,\n\t\t\tcontentType?: string,\n\t\t): Promise<KnowledgeFileEntry> => {\n\t\t\tconst res = await this.#http.request<{ file: KnowledgeFileEntry }>(`${this.base}/files`, {\n\t\t\t\tmethod: 'PUT',\n\t\t\t\tbytes,\n\t\t\t\tquery: { path },\n\t\t\t\t...(contentType && { contentType }),\n\t\t\t});\n\t\t\treturn res.file;\n\t\t},\n\n\t\t/** List files, optionally under one prefix. Ordered by path, so a listing\n\t\t * reads like a directory rather than an upload log. */\n\t\tlist: async (prefix = '/'): Promise<KnowledgeFileEntry[]> => {\n\t\t\tconst res = await this.#http.get<{ files: KnowledgeFileEntry[] }>(`${this.base}/files`, {\n\t\t\t\t...(prefix && prefix !== '/' ? { prefix } : {}),\n\t\t\t});\n\t\t\treturn res.files ?? [];\n\t\t},\n\n\t\t/**\n\t\t * Read a file's bytes back.\n\t\t *\n\t\t * Exists because until now you could write a file and list it but never\n\t\t * read it, so a script had no way to verify its own upload except by\n\t\t * asking an agent.\n\t\t */\n\t\tget: async (path: string): Promise<Uint8Array> => {\n\t\t\t// `send`, not `request`. `request` ALWAYS parses the body as JSON; the\n\t\t\t// `raw` flag it used to be passed was declared in the options type and\n\t\t\t// read by nothing, so this returned a parsed object (or threw \"Expected\n\t\t\t// JSON\") and then called `.arrayBuffer()` on it. Every call failed.\n\t\t\tconst res = await this.#http.send(`${this.base}/files/content`, { query: { path } });\n\t\t\treturn new Uint8Array(await res.arrayBuffer());\n\t\t},\n\n\t\t/**\n\t\t * Read a file back as a string.\n\t\t *\n\t\t * `get` returns bytes, which is right for a PDF and awkward for text: you\n\t\t * decode, change something, and encode again. This is the same file\n\t\t * already decoded, for the case people actually hit, which is reading a\n\t\t * document to check it or to correct it.\n\t\t */\n\t\ttext: async (path: string): Promise<string> => {\n\t\t\t// Same fix as `get` above, and this one failed in a nastier way: for a\n\t\t\t// JSON file `request` PARSED it and returned an object, so the failure\n\t\t\t// was `.text is not a function`; for markdown it threw \"Expected JSON\n\t\t\t// from ...\". Two different errors, one cause, neither mentioning it.\n\t\t\tconst res = await this.#http.send(`${this.base}/files/content`, { query: { path } });\n\t\t\treturn res.text();\n\t\t},\n\n\t\tremove: async (path: string): Promise<void> => {\n\t\t\tawait this.#http.delete(`${this.base}/files`, { path });\n\t\t},\n\n\t\t/**\n\t\t * Prepare a file again.\n\t\t *\n\t\t * Preparation happens by itself when a file is uploaded, so this is for\n\t\t * the times it did not work: a scan that came out blank, or a document\n\t\t * that failed before we could read its format and can now.\n\t\t *\n\t\t * It also hands back a file you have edited by hand. Anything you write\n\t\t * yourself is left alone by later runs, and calling this returns it to\n\t\t * being generated.\n\t\t *\n\t\t * Returns immediately. Watch `status` on the file listing to see it\n\t\t * finish.\n\t\t */\n\t\tretry: async (path: string): Promise<void> => {\n\t\t\tawait this.#http.post(`${this.base}/files/retry`, { path });\n\t\t},\n\n\t\t/**\n\t\t * Make the base hold exactly these files: write each one, then remove\n\t\t * every file not among them.\n\t\t *\n\t\t * For an app that keeps a base in step with a source it reads again (a\n\t\t * website, a store): a page the source dropped leaves the base too.\n\t\t * Paths are compared the way the API stores them, with a leading slash,\n\t\t * so `pages/home.md` and `/pages/home.md` are the same file. Comparing\n\t\t * them as written once removed every file a sync had just written.\n\t\t */\n\t\tmirror: async (\n\t\t\tfiles: { path: string; text: string; contentType?: string }[],\n\t\t): Promise<{ written: number; removed: number }> => {\n\t\t\tconst wanted = new Set(files.map((f) => storedPath(f.path)));\n\t\t\tfor (const file of files) {\n\t\t\t\tawait this.files.put(file.path, new TextEncoder().encode(file.text), file.contentType ?? 'text/markdown');\n\t\t\t}\n\t\t\tlet removed = 0;\n\t\t\tfor (const entry of await this.files.list()) {\n\t\t\t\tif (wanted.has(storedPath(entry.path))) continue;\n\t\t\t\tawait this.files.remove(entry.path);\n\t\t\t\tremoved++;\n\t\t\t}\n\t\t\treturn { written: files.length, removed };\n\t\t},\n\n\t\t/**\n\t\t * Upload a whole directory, preserving its structure under `prefix`.\n\t\t *\n\t\t * Sugar over repeated `put`, but it is the operation people actually\n\t\t * perform, and hand-rolling the walk is where mistakes about path prefixes\n\t\t * get made. Node and Bun only: it reads the local filesystem.\n\t\t *\n\t\t * Sequential on purpose. A knowledge base is uploaded once in a while, not\n\t\t * in a hot path, and a burst of parallel writes against one base is a good\n\t\t * way to meet a rate limit for no gain.\n\t\t */\n\t\tputDirectory: async (\n\t\t\tdir: string,\n\t\t\toptions: { prefix?: string; include?: string[] } = {},\n\t\t): Promise<KnowledgeFileEntry[]> => {\n\t\t\tconst { readdir, readFile } = await import('node:fs/promises');\n\t\t\tconst { join, relative, posix, sep } = await import('node:path');\n\n\t\t\tconst prefix = (options.prefix ?? '/').replace(/\\/+$/, '');\n\t\t\tconst matches = (rel: string): boolean => {\n\t\t\t\tif (!options.include || options.include.length === 0) return true;\n\t\t\t\treturn options.include.some((pattern) => globMatches(pattern, rel));\n\t\t\t};\n\n\t\t\tconst walk = async (current: string): Promise<string[]> => {\n\t\t\t\tconst entries = await readdir(current, { withFileTypes: true });\n\t\t\t\tconst out: string[] = [];\n\t\t\t\tfor (const e of entries) {\n\t\t\t\t\tconst full = join(current, e.name);\n\t\t\t\t\tif (e.isDirectory()) out.push(...(await walk(full)));\n\t\t\t\t\telse if (e.isFile()) out.push(full);\n\t\t\t\t}\n\t\t\t\treturn out;\n\t\t\t};\n\n\t\t\tconst uploaded: KnowledgeFileEntry[] = [];\n\t\t\tfor (const full of await walk(dir)) {\n\t\t\t\tconst rel = relative(dir, full).split(sep).join(posix.sep);\n\t\t\t\tif (!matches(rel)) continue;\n\t\t\t\tconst bytes = await readFile(full);\n\t\t\t\tuploaded.push(\n\t\t\t\t\tawait this.files.put(\n\t\t\t\t\t\t`${prefix}/${rel}`,\n\t\t\t\t\t\tnew Uint8Array(bytes),\n\t\t\t\t\t\tcontentTypeFor(rel),\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn uploaded;\n\t\t},\n\t};\n\n\t/**\n\t * The agent that looks after this base.\n\t *\n\t * Every base has one. It reads the documents you upload, turns the ones an\n\t * agent cannot open into text, and keeps a list of what is in here. That\n\t * happens by itself; these methods are for asking it to do something\n\t * specific.\n\t *\n\t * It lives in your own workspace, so its runs cost turns like any other\n\t * agent's, and you can see what it did.\n\t */\n\treadonly librarian = {\n\t\t/**\n\t\t * Work through the whole base: read what has not been read, and rewrite\n\t\t * the list of what is in here.\n\t\t *\n\t\t * The same routine every time, which is the difference between this and\n\t\t * `ask`. Reach for it after uploading a lot at once, or when something\n\t\t * looks out of date.\n\t\t *\n\t\t * RETURNS IMMEDIATELY. The work is queued and runs within about a minute,\n\t\t * because reading a base can take several minutes and no HTTP call should\n\t\t * be held open that long. Watch `status` on the file listing to see it\n\t\t * finish, exactly as with `files.retry`.\n\t\t */\n\t\torganize: async (): Promise<void> => {\n\t\t\tawait this.#http.post(`${this.base}/librarian/organize`, {});\n\t\t},\n\n\t\t/**\n\t\t * Ask the librarian to do something to this base.\n\t\t *\n\t\t * 'split the handbook by chapter'\n\t\t * 'the text from that scan came out garbled, read it again'\n\t\t * 'what is in here about pricing?'\n\t\t *\n\t\t * It can add and change files, which is its job. Your documents are not\n\t\t * sacred to it, so ask it for what you want rather than working around it.\n\t\t */\n\t\task: async (text: string): Promise<{ reply: string }> => {\n\t\t\treturn this.#http.post<{ reply: string }>(`${this.base}/librarian/ask`, { text });\n\t\t},\n\t};\n}\n\n/**\n * Enough glob for `**\\/*.md` and `docs/*.txt`, which is what `include` is for.\n *\n * Deliberately not a glob library. Pulling a dependency into a published SDK so\n * that one convenience method can match filenames is a poor trade, and the\n * patterns people pass here are simple. Anything more complex: list the files\n * yourself and call `put`.\n */\nfunction globMatches(pattern: string, path: string): boolean {\n\tconst rx = pattern\n\t\t.split('/')\n\t\t.map((seg) => {\n\t\t\tif (seg === '**') return '.*';\n\t\t\treturn seg.replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&').replace(/\\*/g, '[^/]*');\n\t\t})\n\t\t.join('/')\n\t\t// `**/` should also match zero directories, so `**\\/*.md` finds `a.md`.\n\t\t.replace(/\\.\\*\\//g, '(?:.*/)?');\n\treturn new RegExp(`^${rx}$`).test(path);\n}\n\n/** A content type from the extension, so uploads are not all octet-stream. */\nfunction contentTypeFor(path: string): string {\n\tconst ext = path.slice(path.lastIndexOf('.') + 1).toLowerCase();\n\tconst known: Record<string, string> = {\n\t\tmd: 'text/markdown',\n\t\tmarkdown: 'text/markdown',\n\t\ttxt: 'text/plain',\n\t\tcsv: 'text/csv',\n\t\tjson: 'application/json',\n\t\thtml: 'text/html',\n\t\tpdf: 'application/pdf',\n\t};\n\treturn known[ext] ?? 'application/octet-stream';\n}\n"]}
|
package/package.json
CHANGED
package/partner-app.d.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
export interface PartnerAppOptions {
|
|
2
|
+
/** The client id AgentNava registered for your app (`pa_...`). */
|
|
3
|
+
clientId: string;
|
|
4
|
+
/** Its secret (`pas_...`), shown once at registration. Needed by exchangeCode and grantCredits. */
|
|
5
|
+
clientSecret?: string;
|
|
6
|
+
/** The API. Defaults to `AGENTNAVA_BASE_URL`, then `https://api.agentnava.com`. */
|
|
7
|
+
baseURL?: string;
|
|
8
|
+
/** Where people sign in. Defaults to `AGENTNAVA_APP_URL`, then `https://agentnava.com`. */
|
|
9
|
+
appURL?: string;
|
|
10
|
+
/** Per-request timeout in milliseconds. Default 30_000. */
|
|
11
|
+
timeout?: number;
|
|
12
|
+
/** Swap the fetch implementation, for tests or a proxy. */
|
|
13
|
+
fetch?: typeof globalThis.fetch;
|
|
14
|
+
}
|
|
15
|
+
export interface ExchangedCode {
|
|
16
|
+
/** A workspace API key. It does not expire; the workspace's owner can remove it. */
|
|
17
|
+
apiKey: string;
|
|
18
|
+
workspaceId: string;
|
|
19
|
+
/** What the person allowed, as registered: `agents`, and `credits` for an app that bills. */
|
|
20
|
+
permissions: string[];
|
|
21
|
+
/** The API the key belongs to, the one this exchange used. Pass it to AgentNava.getWorkspace with the key. */
|
|
22
|
+
baseURL: string;
|
|
23
|
+
}
|
|
24
|
+
export interface CreditGrant {
|
|
25
|
+
/** False when this reference was already credited: nothing was added this time. */
|
|
26
|
+
granted: boolean;
|
|
27
|
+
balanceCents: number;
|
|
28
|
+
}
|
|
29
|
+
export declare class PartnerApp {
|
|
30
|
+
#private;
|
|
31
|
+
readonly clientId: string;
|
|
32
|
+
constructor(options: PartnerAppOptions);
|
|
33
|
+
/** A fresh PKCE pair. Keep `verifier` for exchangeCode; send `challenge` in the link. */
|
|
34
|
+
pkce(): Promise<{
|
|
35
|
+
verifier: string;
|
|
36
|
+
challenge: string;
|
|
37
|
+
}>;
|
|
38
|
+
/**
|
|
39
|
+
* Where to send the person to sign in and allow your app. `redirectUri` must be one you registered.
|
|
40
|
+
* `installId` names one install of your app (a store, say): signing in again then replaces only that
|
|
41
|
+
* install's earlier key, so two installs in one workspace each keep theirs. Letters, digits and . _ : - only.
|
|
42
|
+
* `workspaceName`, for an app that bills its workspaces: the name of the new workspace the person is
|
|
43
|
+
* offered for this install ("Copperleaf Pantry (Shopify)"), one per install. Up to 80 characters.
|
|
44
|
+
*/
|
|
45
|
+
authorizeUrl(args: {
|
|
46
|
+
redirectUri: string;
|
|
47
|
+
state: string;
|
|
48
|
+
codeChallenge: string;
|
|
49
|
+
installId?: string;
|
|
50
|
+
workspaceName?: string;
|
|
51
|
+
}): string;
|
|
52
|
+
/** Swap the one-time code your redirect received for the workspace's API key. A code works once. */
|
|
53
|
+
exchangeCode(args: {
|
|
54
|
+
code: string;
|
|
55
|
+
redirectUri: string;
|
|
56
|
+
codeVerifier: string;
|
|
57
|
+
}): Promise<ExchangedCode>;
|
|
58
|
+
/**
|
|
59
|
+
* Add credit the person paid for through your platform. Once per `reference`: sending it again adds nothing.
|
|
60
|
+
* With `billingUrl` (https), your app becomes the workspace's billing source: AgentNava's billing screens name your app and link to this page to manage the plan.
|
|
61
|
+
* `billingUrl: null` (or '') clears it, so AgentNava bills the workspace again; leaving it out changes nothing.
|
|
62
|
+
*/
|
|
63
|
+
grantCredits(args: {
|
|
64
|
+
workspaceId: string;
|
|
65
|
+
amountCents: number;
|
|
66
|
+
reference: string;
|
|
67
|
+
note: string;
|
|
68
|
+
billingUrl?: string | null;
|
|
69
|
+
}): Promise<CreditGrant>;
|
|
70
|
+
}
|
package/partner-app.js
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// Sign in with AgentNava, for a partner app's own server: an app another team
|
|
2
|
+
// builds on AgentNava, which asks a person to let it use one of their
|
|
3
|
+
// workspaces. Standard OAuth 2.0 authorization code with PKCE (S256); what the
|
|
4
|
+
// exchange returns is an ordinary workspace API key, for AgentNava.getWorkspace.
|
|
5
|
+
//
|
|
6
|
+
// const app = AgentNava.partnerApp({ clientId, clientSecret });
|
|
7
|
+
// const { verifier, challenge } = await app.pkce(); // keep the verifier
|
|
8
|
+
// redirect(app.authorizeUrl({ redirectUri, state, codeChallenge: challenge }));
|
|
9
|
+
// // ... the person comes back to redirectUri with ?code&state
|
|
10
|
+
// const { apiKey, baseURL } = await app.exchangeCode({ code, redirectUri, codeVerifier: verifier });
|
|
11
|
+
// const ws = AgentNava.getWorkspace({ apiKey, baseURL });
|
|
12
|
+
//
|
|
13
|
+
// The client secret belongs on a server. Never ship it to a browser.
|
|
14
|
+
import { AgentNavaError } from "./errors.js";
|
|
15
|
+
import { Http } from "./http.js";
|
|
16
|
+
function readEnv(name) {
|
|
17
|
+
try {
|
|
18
|
+
return globalThis.process?.env?.[name];
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function base64Url(bytes) {
|
|
25
|
+
let s = '';
|
|
26
|
+
for (const b of bytes)
|
|
27
|
+
s += String.fromCharCode(b);
|
|
28
|
+
return btoa(s).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
29
|
+
}
|
|
30
|
+
/** Web Crypto: a global everywhere this SDK runs except Node 18, which keeps it in node:crypto. */
|
|
31
|
+
async function webCrypto() {
|
|
32
|
+
if (globalThis.crypto?.subtle)
|
|
33
|
+
return globalThis.crypto;
|
|
34
|
+
const { webcrypto } = await import('node:crypto');
|
|
35
|
+
return webcrypto;
|
|
36
|
+
}
|
|
37
|
+
export class PartnerApp {
|
|
38
|
+
clientId;
|
|
39
|
+
#baseURL;
|
|
40
|
+
#appURL;
|
|
41
|
+
/** Signed in with the app's id and secret (HTTP Basic, RFC 6749 section 2.3.1); null without a secret. */
|
|
42
|
+
#http;
|
|
43
|
+
constructor(options) {
|
|
44
|
+
if (!options?.clientId) {
|
|
45
|
+
throw new AgentNavaError({ message: 'clientId is required', code: 'missing_client_id', status: 0 });
|
|
46
|
+
}
|
|
47
|
+
this.clientId = options.clientId;
|
|
48
|
+
this.#baseURL = (options.baseURL ?? readEnv('AGENTNAVA_BASE_URL') ?? 'https://api.agentnava.com').replace(/\/+$/, '');
|
|
49
|
+
this.#appURL = (options.appURL ?? readEnv('AGENTNAVA_APP_URL') ?? 'https://agentnava.com').replace(/\/+$/, '');
|
|
50
|
+
const secret = options.clientSecret;
|
|
51
|
+
this.#http = secret
|
|
52
|
+
? new Http({ baseURL: this.#baseURL, timeout: options.timeout ?? 30_000, ...(options.fetch && { fetch: options.fetch }) }, `Basic ${btoa(`${encodeURIComponent(this.clientId)}:${encodeURIComponent(secret)}`)}`)
|
|
53
|
+
: null;
|
|
54
|
+
}
|
|
55
|
+
/** A fresh PKCE pair. Keep `verifier` for exchangeCode; send `challenge` in the link. */
|
|
56
|
+
async pkce() {
|
|
57
|
+
const webcrypto = await webCrypto();
|
|
58
|
+
const verifier = base64Url(webcrypto.getRandomValues(new Uint8Array(32)));
|
|
59
|
+
const digest = new Uint8Array(await webcrypto.subtle.digest('SHA-256', new TextEncoder().encode(verifier)));
|
|
60
|
+
return { verifier, challenge: base64Url(digest) };
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Where to send the person to sign in and allow your app. `redirectUri` must be one you registered.
|
|
64
|
+
* `installId` names one install of your app (a store, say): signing in again then replaces only that
|
|
65
|
+
* install's earlier key, so two installs in one workspace each keep theirs. Letters, digits and . _ : - only.
|
|
66
|
+
* `workspaceName`, for an app that bills its workspaces: the name of the new workspace the person is
|
|
67
|
+
* offered for this install ("Copperleaf Pantry (Shopify)"), one per install. Up to 80 characters.
|
|
68
|
+
*/
|
|
69
|
+
authorizeUrl(args) {
|
|
70
|
+
const url = new URL(`${this.#appURL}/authorize`);
|
|
71
|
+
url.searchParams.set('response_type', 'code');
|
|
72
|
+
url.searchParams.set('client_id', this.clientId);
|
|
73
|
+
url.searchParams.set('redirect_uri', args.redirectUri);
|
|
74
|
+
url.searchParams.set('state', args.state);
|
|
75
|
+
url.searchParams.set('code_challenge', args.codeChallenge);
|
|
76
|
+
url.searchParams.set('code_challenge_method', 'S256');
|
|
77
|
+
if (args.installId)
|
|
78
|
+
url.searchParams.set('install_id', args.installId);
|
|
79
|
+
if (args.workspaceName)
|
|
80
|
+
url.searchParams.set('workspace_name', args.workspaceName);
|
|
81
|
+
return url.toString();
|
|
82
|
+
}
|
|
83
|
+
/** Swap the one-time code your redirect received for the workspace's API key. A code works once. */
|
|
84
|
+
async exchangeCode(args) {
|
|
85
|
+
const form = new URLSearchParams({
|
|
86
|
+
grant_type: 'authorization_code',
|
|
87
|
+
code: args.code,
|
|
88
|
+
redirect_uri: args.redirectUri,
|
|
89
|
+
code_verifier: args.codeVerifier,
|
|
90
|
+
});
|
|
91
|
+
const out = await this.#signedIn().request('/v1/oauth/token', {
|
|
92
|
+
method: 'POST',
|
|
93
|
+
bytes: new TextEncoder().encode(form.toString()),
|
|
94
|
+
contentType: 'application/x-www-form-urlencoded',
|
|
95
|
+
});
|
|
96
|
+
return {
|
|
97
|
+
apiKey: out.access_token,
|
|
98
|
+
workspaceId: out.workspace_id,
|
|
99
|
+
permissions: (out.scope ?? '').split(' ').filter(Boolean),
|
|
100
|
+
baseURL: this.#baseURL,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Add credit the person paid for through your platform. Once per `reference`: sending it again adds nothing.
|
|
105
|
+
* With `billingUrl` (https), your app becomes the workspace's billing source: AgentNava's billing screens name your app and link to this page to manage the plan.
|
|
106
|
+
* `billingUrl: null` (or '') clears it, so AgentNava bills the workspace again; leaving it out changes nothing.
|
|
107
|
+
*/
|
|
108
|
+
async grantCredits(args) {
|
|
109
|
+
const out = await this.#signedIn().post('/v1/partner/credits', {
|
|
110
|
+
workspace_id: args.workspaceId,
|
|
111
|
+
amount_cents: args.amountCents,
|
|
112
|
+
reference: args.reference,
|
|
113
|
+
note: args.note,
|
|
114
|
+
...(args.billingUrl !== undefined && { billing_url: args.billingUrl || null }),
|
|
115
|
+
});
|
|
116
|
+
return { granted: out.granted, balanceCents: out.balance_cents };
|
|
117
|
+
}
|
|
118
|
+
#signedIn() {
|
|
119
|
+
if (!this.#http) {
|
|
120
|
+
throw new AgentNavaError({ message: 'clientSecret is required on the server', code: 'missing_client_secret', status: 0 });
|
|
121
|
+
}
|
|
122
|
+
return this.#http;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=partner-app.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"partner-app.js","sourceRoot":"","sources":["../src/partner-app.ts"],"names":[],"mappings":"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","sourcesContent":["// Sign in with AgentNava, for a partner app's own server: an app another team\n// builds on AgentNava, which asks a person to let it use one of their\n// workspaces. Standard OAuth 2.0 authorization code with PKCE (S256); what the\n// exchange returns is an ordinary workspace API key, for AgentNava.getWorkspace.\n//\n// const app = AgentNava.partnerApp({ clientId, clientSecret });\n// const { verifier, challenge } = await app.pkce(); // keep the verifier\n// redirect(app.authorizeUrl({ redirectUri, state, codeChallenge: challenge }));\n// // ... the person comes back to redirectUri with ?code&state\n// const { apiKey, baseURL } = await app.exchangeCode({ code, redirectUri, codeVerifier: verifier });\n// const ws = AgentNava.getWorkspace({ apiKey, baseURL });\n//\n// The client secret belongs on a server. Never ship it to a browser.\n\nimport { AgentNavaError } from './errors.ts';\nimport { Http } from './http.ts';\n\nexport interface PartnerAppOptions {\n\t/** The client id AgentNava registered for your app (`pa_...`). */\n\tclientId: string;\n\t/** Its secret (`pas_...`), shown once at registration. Needed by exchangeCode and grantCredits. */\n\tclientSecret?: string;\n\t/** The API. Defaults to `AGENTNAVA_BASE_URL`, then `https://api.agentnava.com`. */\n\tbaseURL?: string;\n\t/** Where people sign in. Defaults to `AGENTNAVA_APP_URL`, then `https://agentnava.com`. */\n\tappURL?: string;\n\t/** Per-request timeout in milliseconds. Default 30_000. */\n\ttimeout?: number;\n\t/** Swap the fetch implementation, for tests or a proxy. */\n\tfetch?: typeof globalThis.fetch;\n}\n\nexport interface ExchangedCode {\n\t/** A workspace API key. It does not expire; the workspace's owner can remove it. */\n\tapiKey: string;\n\tworkspaceId: string;\n\t/** What the person allowed, as registered: `agents`, and `credits` for an app that bills. */\n\tpermissions: string[];\n\t/** The API the key belongs to, the one this exchange used. Pass it to AgentNava.getWorkspace with the key. */\n\tbaseURL: string;\n}\n\nexport interface CreditGrant {\n\t/** False when this reference was already credited: nothing was added this time. */\n\tgranted: boolean;\n\tbalanceCents: number;\n}\n\nfunction readEnv(name: string): string | undefined {\n\ttry {\n\t\treturn (globalThis as { process?: { env?: Record<string, string | undefined> } }).process?.env?.[name];\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction base64Url(bytes: Uint8Array): string {\n\tlet s = '';\n\tfor (const b of bytes) s += String.fromCharCode(b);\n\treturn btoa(s).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\n\n/** Web Crypto: a global everywhere this SDK runs except Node 18, which keeps it in node:crypto. */\nasync function webCrypto(): Promise<Crypto> {\n\tif (globalThis.crypto?.subtle) return globalThis.crypto;\n\tconst { webcrypto } = await import('node:crypto');\n\treturn webcrypto as unknown as Crypto;\n}\n\nexport class PartnerApp {\n\treadonly clientId: string;\n\treadonly #baseURL: string;\n\treadonly #appURL: string;\n\t/** Signed in with the app's id and secret (HTTP Basic, RFC 6749 section 2.3.1); null without a secret. */\n\treadonly #http: Http | null;\n\n\tconstructor(options: PartnerAppOptions) {\n\t\tif (!options?.clientId) {\n\t\t\tthrow new AgentNavaError({ message: 'clientId is required', code: 'missing_client_id', status: 0 });\n\t\t}\n\t\tthis.clientId = options.clientId;\n\t\tthis.#baseURL = (options.baseURL ?? readEnv('AGENTNAVA_BASE_URL') ?? 'https://api.agentnava.com').replace(/\\/+$/, '');\n\t\tthis.#appURL = (options.appURL ?? readEnv('AGENTNAVA_APP_URL') ?? 'https://agentnava.com').replace(/\\/+$/, '');\n\t\tconst secret = options.clientSecret;\n\t\tthis.#http = secret\n\t\t\t? new Http(\n\t\t\t\t\t{ baseURL: this.#baseURL, timeout: options.timeout ?? 30_000, ...(options.fetch && { fetch: options.fetch }) },\n\t\t\t\t\t`Basic ${btoa(`${encodeURIComponent(this.clientId)}:${encodeURIComponent(secret)}`)}`,\n\t\t\t\t)\n\t\t\t: null;\n\t}\n\n\t/** A fresh PKCE pair. Keep `verifier` for exchangeCode; send `challenge` in the link. */\n\tasync pkce(): Promise<{ verifier: string; challenge: string }> {\n\t\tconst webcrypto = await webCrypto();\n\t\tconst verifier = base64Url(webcrypto.getRandomValues(new Uint8Array(32)));\n\t\tconst digest = new Uint8Array(await webcrypto.subtle.digest('SHA-256', new TextEncoder().encode(verifier)));\n\t\treturn { verifier, challenge: base64Url(digest) };\n\t}\n\n\t/**\n\t * Where to send the person to sign in and allow your app. `redirectUri` must be one you registered.\n\t * `installId` names one install of your app (a store, say): signing in again then replaces only that\n\t * install's earlier key, so two installs in one workspace each keep theirs. Letters, digits and . _ : - only.\n\t * `workspaceName`, for an app that bills its workspaces: the name of the new workspace the person is\n\t * offered for this install (\"Copperleaf Pantry (Shopify)\"), one per install. Up to 80 characters.\n\t */\n\tauthorizeUrl(args: { redirectUri: string; state: string; codeChallenge: string; installId?: string; workspaceName?: string }): string {\n\t\tconst url = new URL(`${this.#appURL}/authorize`);\n\t\turl.searchParams.set('response_type', 'code');\n\t\turl.searchParams.set('client_id', this.clientId);\n\t\turl.searchParams.set('redirect_uri', args.redirectUri);\n\t\turl.searchParams.set('state', args.state);\n\t\turl.searchParams.set('code_challenge', args.codeChallenge);\n\t\turl.searchParams.set('code_challenge_method', 'S256');\n\t\tif (args.installId) url.searchParams.set('install_id', args.installId);\n\t\tif (args.workspaceName) url.searchParams.set('workspace_name', args.workspaceName);\n\t\treturn url.toString();\n\t}\n\n\t/** Swap the one-time code your redirect received for the workspace's API key. A code works once. */\n\tasync exchangeCode(args: { code: string; redirectUri: string; codeVerifier: string }): Promise<ExchangedCode> {\n\t\tconst form = new URLSearchParams({\n\t\t\tgrant_type: 'authorization_code',\n\t\t\tcode: args.code,\n\t\t\tredirect_uri: args.redirectUri,\n\t\t\tcode_verifier: args.codeVerifier,\n\t\t});\n\t\tconst out = await this.#signedIn().request<{ access_token: string; workspace_id: string; scope?: string }>('/v1/oauth/token', {\n\t\t\tmethod: 'POST',\n\t\t\tbytes: new TextEncoder().encode(form.toString()),\n\t\t\tcontentType: 'application/x-www-form-urlencoded',\n\t\t});\n\t\treturn {\n\t\t\tapiKey: out.access_token,\n\t\t\tworkspaceId: out.workspace_id,\n\t\t\tpermissions: (out.scope ?? '').split(' ').filter(Boolean),\n\t\t\tbaseURL: this.#baseURL,\n\t\t};\n\t}\n\n\t/**\n\t * Add credit the person paid for through your platform. Once per `reference`: sending it again adds nothing.\n\t * With `billingUrl` (https), your app becomes the workspace's billing source: AgentNava's billing screens name your app and link to this page to manage the plan.\n\t * `billingUrl: null` (or '') clears it, so AgentNava bills the workspace again; leaving it out changes nothing.\n\t */\n\tasync grantCredits(args: { workspaceId: string; amountCents: number; reference: string; note: string; billingUrl?: string | null }): Promise<CreditGrant> {\n\t\tconst out = await this.#signedIn().post<{ granted: boolean; balance_cents: number }>('/v1/partner/credits', {\n\t\t\tworkspace_id: args.workspaceId,\n\t\t\tamount_cents: args.amountCents,\n\t\t\treference: args.reference,\n\t\t\tnote: args.note,\n\t\t\t...(args.billingUrl !== undefined && { billing_url: args.billingUrl || null }),\n\t\t});\n\t\treturn { granted: out.granted, balanceCents: out.balance_cents };\n\t}\n\n\t#signedIn(): Http {\n\t\tif (!this.#http) {\n\t\t\tthrow new AgentNavaError({ message: 'clientSecret is required on the server', code: 'missing_client_secret', status: 0 });\n\t\t}\n\t\treturn this.#http;\n\t}\n}\n"]}
|