@stigmer/sdk 3.2.3 → 3.4.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/__tests__/errors.test.js +44 -1
- package/__tests__/errors.test.js.map +1 -1
- package/__tests__/manifest.test.d.ts +2 -0
- package/__tests__/manifest.test.d.ts.map +1 -0
- package/__tests__/manifest.test.js +229 -0
- package/__tests__/manifest.test.js.map +1 -0
- package/__tests__/session.test.d.ts +6 -0
- package/__tests__/session.test.d.ts.map +1 -0
- package/__tests__/session.test.js +50 -0
- package/__tests__/session.test.js.map +1 -0
- package/billing.d.ts +83 -1
- package/billing.d.ts.map +1 -1
- package/billing.js +91 -1
- package/billing.js.map +1 -1
- package/cursor-accounts.d.ts +94 -0
- package/cursor-accounts.d.ts.map +1 -0
- package/cursor-accounts.js +119 -0
- package/cursor-accounts.js.map +1 -0
- package/errors.d.ts +14 -0
- package/errors.d.ts.map +1 -1
- package/errors.js +26 -0
- package/errors.js.map +1 -1
- package/gen/agent.d.ts +5 -0
- package/gen/agent.d.ts.map +1 -1
- package/gen/agent.js +9 -1
- package/gen/agent.js.map +1 -1
- package/gen/agentexecution.d.ts +1 -0
- package/gen/agentexecution.d.ts.map +1 -1
- package/gen/agentexecution.js +2 -0
- package/gen/agentexecution.js.map +1 -1
- package/gen/agentinstance.d.ts +1 -0
- package/gen/agentinstance.d.ts.map +1 -1
- package/gen/agentinstance.js +1 -0
- package/gen/agentinstance.js.map +1 -1
- package/gen/authorization-config.d.ts.map +1 -1
- package/gen/authorization-config.js +1 -0
- package/gen/authorization-config.js.map +1 -1
- package/gen/client.d.ts +6 -2
- package/gen/client.d.ts.map +1 -1
- package/gen/client.js +4 -0
- package/gen/client.js.map +1 -1
- package/gen/datastore.d.ts +129 -0
- package/gen/datastore.d.ts.map +1 -0
- package/gen/datastore.js +303 -0
- package/gen/datastore.js.map +1 -0
- package/gen/session.d.ts +1 -0
- package/gen/session.d.ts.map +1 -1
- package/gen/session.js +1 -0
- package/gen/session.js.map +1 -1
- package/index.d.ts +6 -4
- package/index.d.ts.map +1 -1
- package/index.js +8 -4
- package/index.js.map +1 -1
- package/manifest/client.d.ts +64 -0
- package/manifest/client.d.ts.map +1 -0
- package/manifest/client.js +109 -0
- package/manifest/client.js.map +1 -0
- package/manifest/index.d.ts +9 -0
- package/manifest/index.d.ts.map +1 -0
- package/manifest/index.js +11 -0
- package/manifest/index.js.map +1 -0
- package/manifest/parse.d.ts +57 -0
- package/manifest/parse.d.ts.map +1 -0
- package/manifest/parse.js +134 -0
- package/manifest/parse.js.map +1 -0
- package/manifest/redaction.d.ts +16 -0
- package/manifest/redaction.d.ts.map +1 -0
- package/manifest/redaction.js +19 -0
- package/manifest/redaction.js.map +1 -0
- package/manifest/registry.d.ts +44 -0
- package/manifest/registry.d.ts.map +1 -0
- package/manifest/registry.js +178 -0
- package/manifest/registry.js.map +1 -0
- package/manifest/serialize.d.ts +24 -0
- package/manifest/serialize.d.ts.map +1 -0
- package/manifest/serialize.js +78 -0
- package/manifest/serialize.js.map +1 -0
- package/package.json +4 -3
- package/session.d.ts +37 -0
- package/session.d.ts.map +1 -1
- package/session.js +43 -0
- package/session.js.map +1 -1
- package/src/__tests__/errors.test.ts +58 -0
- package/src/__tests__/manifest.test.ts +271 -0
- package/src/__tests__/session.test.ts +66 -0
- package/src/billing.ts +160 -0
- package/src/cursor-accounts.ts +207 -0
- package/src/errors.ts +27 -0
- package/src/gen/agent.ts +15 -1
- package/src/gen/agentexecution.ts +2 -0
- package/src/gen/agentinstance.ts +2 -0
- package/src/gen/authorization-config.ts +1 -0
- package/src/gen/client.ts +7 -2
- package/src/gen/datastore.ts +381 -0
- package/src/gen/session.ts +2 -0
- package/src/index.ts +36 -1
- package/src/manifest/client.ts +145 -0
- package/src/manifest/index.ts +25 -0
- package/src/manifest/parse.ts +204 -0
- package/src/manifest/redaction.ts +20 -0
- package/src/manifest/registry.ts +232 -0
- package/src/manifest/serialize.ts +92 -0
- package/src/session.ts +48 -0
- package/src/stigmer.ts +8 -0
- package/stigmer.d.ts +6 -0
- package/stigmer.d.ts.map +1 -1
- package/stigmer.js +8 -0
- package/stigmer.js.map +1 -1
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
// Strict YAML → proto parsing for Stigmer resource manifests.
|
|
2
|
+
//
|
|
3
|
+
// The strictness contract matches `stigmer apply -f`: YAML syntax errors and
|
|
4
|
+
// unknown fields both fail loudly (`ignoreUnknownFields: false`) — silently
|
|
5
|
+
// applying a half-parsed or typo'd document would be dangerous. Error
|
|
6
|
+
// messages are written for end users (DD-006): what failed, where, and what
|
|
7
|
+
// a valid document looks like.
|
|
8
|
+
|
|
9
|
+
import { fromJson, type JsonValue, type Message } from "@bufbuild/protobuf";
|
|
10
|
+
import { create } from "@bufbuild/protobuf";
|
|
11
|
+
import {
|
|
12
|
+
type ApiResourceMetadata,
|
|
13
|
+
ApiResourceMetadataSchema,
|
|
14
|
+
} from "@stigmer/protos/ai/stigmer/commons/apiresource/metadata_pb";
|
|
15
|
+
import { parseAllDocuments } from "yaml";
|
|
16
|
+
import {
|
|
17
|
+
type ManifestKindHandler,
|
|
18
|
+
manifestHandlerForYamlKind,
|
|
19
|
+
manifestKinds,
|
|
20
|
+
} from "./registry.js";
|
|
21
|
+
|
|
22
|
+
/** Options for {@link parseManifest}. */
|
|
23
|
+
export interface ParseManifestOptions {
|
|
24
|
+
/**
|
|
25
|
+
* Target organization slug. Injected into `metadata.org` when the document
|
|
26
|
+
* omits it. When the document specifies a *different* org, the document's
|
|
27
|
+
* value wins and a warning is attached (matching `stigmer apply`).
|
|
28
|
+
*/
|
|
29
|
+
readonly org?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** One resource document parsed from a manifest. */
|
|
33
|
+
export interface ManifestDocument {
|
|
34
|
+
/** The registry handler for this document's kind. */
|
|
35
|
+
readonly handler: ManifestKindHandler;
|
|
36
|
+
/** The fully-marshalled resource proto, ready for `apply`. */
|
|
37
|
+
readonly message: Message;
|
|
38
|
+
/** `metadata.name` (always present — validated). */
|
|
39
|
+
readonly name: string;
|
|
40
|
+
/** `metadata.slug`, or the name when the document has no explicit slug. */
|
|
41
|
+
readonly slug: string;
|
|
42
|
+
/** `metadata.org` after org injection ("" when unresolvable). */
|
|
43
|
+
readonly org: string;
|
|
44
|
+
/** Org-mismatch warning, when the document's org differs from the target. */
|
|
45
|
+
readonly warning?: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Parse a (possibly multi-document) YAML manifest into resource protos.
|
|
50
|
+
*
|
|
51
|
+
* Each document is validated against its kind's generated proto schema —
|
|
52
|
+
* the same schemas the backend serves — so a document that parses here is
|
|
53
|
+
* structurally valid for `apply`. Documents are returned in **dependency
|
|
54
|
+
* apply order** (referenced kinds before dependents, stable within a kind),
|
|
55
|
+
* mirroring the CLI's apply ordering.
|
|
56
|
+
*
|
|
57
|
+
* @param content - Raw YAML text (one or more `---`-separated documents).
|
|
58
|
+
* @param options - Optional target org for `metadata.org` injection.
|
|
59
|
+
* @returns The parsed documents, sorted into dependency apply order.
|
|
60
|
+
* @throws {Error} With a user-facing message when the YAML is malformed,
|
|
61
|
+
* a document has no `kind`, the kind is unsupported, or a document does
|
|
62
|
+
* not match its proto schema (unknown fields included).
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```ts
|
|
66
|
+
* import { parseManifest } from "@stigmer/sdk";
|
|
67
|
+
*
|
|
68
|
+
* const docs = parseManifest(yamlText, { org: "acme" });
|
|
69
|
+
* for (const doc of docs) {
|
|
70
|
+
* await stigmer.manifest.apply(doc);
|
|
71
|
+
* }
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
export function parseManifest(
|
|
75
|
+
content: string,
|
|
76
|
+
options: ParseManifestOptions = {},
|
|
77
|
+
): ManifestDocument[] {
|
|
78
|
+
if (!content.trim()) {
|
|
79
|
+
throw new Error(
|
|
80
|
+
"The manifest is empty. Paste or upload a Stigmer resource YAML " +
|
|
81
|
+
"(a document with apiVersion, kind, metadata, and spec).",
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const parsed = parseAllDocuments(content);
|
|
86
|
+
const documents: ManifestDocument[] = [];
|
|
87
|
+
|
|
88
|
+
for (const [index, doc] of parsed.entries()) {
|
|
89
|
+
const where = parsed.length === 1 ? "the manifest" : `document ${index + 1}`;
|
|
90
|
+
|
|
91
|
+
if (doc.errors.length > 0) {
|
|
92
|
+
throw new Error(`Invalid YAML in ${where}: ${doc.errors[0].message}`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const value = doc.toJS() as unknown;
|
|
96
|
+
if (value === null || value === undefined) continue; // blank document between separators
|
|
97
|
+
if (typeof value !== "object" || Array.isArray(value)) {
|
|
98
|
+
throw new Error(
|
|
99
|
+
`Invalid YAML in ${where}: expected a mapping document with ` +
|
|
100
|
+
"apiVersion, kind, metadata, and spec.",
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
documents.push(parseDocument(value as Record<string, unknown>, where, options.org));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (documents.length === 0) {
|
|
108
|
+
throw new Error(
|
|
109
|
+
"The manifest contains no resource documents. Each document needs " +
|
|
110
|
+
"at least a kind (e.g. kind: Agent) and metadata.name.",
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Stable sort into dependency order so multi-document manifests apply
|
|
115
|
+
// parents before dependents (e.g. Environment before AgentChannel).
|
|
116
|
+
return documents.sort((a, b) => a.handler.applyOrder - b.handler.applyOrder);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function parseDocument(
|
|
120
|
+
value: Record<string, unknown>,
|
|
121
|
+
where: string,
|
|
122
|
+
org: string | undefined,
|
|
123
|
+
): ManifestDocument {
|
|
124
|
+
const kind = value.kind;
|
|
125
|
+
if (typeof kind !== "string" || kind === "") {
|
|
126
|
+
throw new Error(
|
|
127
|
+
`${capitalize(where)} is missing the required 'kind' field ` +
|
|
128
|
+
"(e.g. kind: Agent).",
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const handler = manifestHandlerForYamlKind(kind);
|
|
133
|
+
if (handler === undefined) {
|
|
134
|
+
const supported = manifestKinds()
|
|
135
|
+
.map((h) => h.yamlKind)
|
|
136
|
+
.join(", ");
|
|
137
|
+
throw new Error(
|
|
138
|
+
`Unsupported resource kind "${kind}" in ${where}. ` +
|
|
139
|
+
`Supported kinds: ${supported}.`,
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
let message: Message;
|
|
144
|
+
try {
|
|
145
|
+
message = fromJson(handler.schema, value as JsonValue, {
|
|
146
|
+
ignoreUnknownFields: false,
|
|
147
|
+
});
|
|
148
|
+
} catch (err) {
|
|
149
|
+
throw new Error(
|
|
150
|
+
`Invalid ${handler.displayName} in ${where}: ${(err as Error).message}`,
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const warning = injectOrg(message, org ?? "");
|
|
155
|
+
|
|
156
|
+
const metadata = metadataOf(message);
|
|
157
|
+
const name = metadata?.name ?? "";
|
|
158
|
+
if (name === "") {
|
|
159
|
+
throw new Error(
|
|
160
|
+
`${capitalize(where)} is missing the required metadata.name field.`,
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
handler,
|
|
166
|
+
message,
|
|
167
|
+
name,
|
|
168
|
+
slug: metadata?.slug || name,
|
|
169
|
+
org: metadata?.org ?? "",
|
|
170
|
+
...(warning !== undefined && { warning }),
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Read a resource message's metadata envelope, if present. */
|
|
175
|
+
export function metadataOf(message: Message): ApiResourceMetadata | undefined {
|
|
176
|
+
return (message as unknown as { metadata?: ApiResourceMetadata }).metadata;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Inject the target org into metadata.org when the document omitted it. When
|
|
180
|
+
// the document specifies a *different* org, the document's value is honored
|
|
181
|
+
// and a warning is returned (matching `stigmer apply`).
|
|
182
|
+
function injectOrg(message: Message, org: string): string | undefined {
|
|
183
|
+
if (org === "") return undefined;
|
|
184
|
+
const holder = message as unknown as { metadata?: ApiResourceMetadata };
|
|
185
|
+
if (holder.metadata === undefined) {
|
|
186
|
+
holder.metadata = create(ApiResourceMetadataSchema, { org });
|
|
187
|
+
return undefined;
|
|
188
|
+
}
|
|
189
|
+
if (holder.metadata.org === "") {
|
|
190
|
+
holder.metadata.org = org;
|
|
191
|
+
return undefined;
|
|
192
|
+
}
|
|
193
|
+
if (holder.metadata.org !== org) {
|
|
194
|
+
return (
|
|
195
|
+
`The document's org "${holder.metadata.org}" differs from the ` +
|
|
196
|
+
`target org "${org}"; applying to "${holder.metadata.org}".`
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
return undefined;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function capitalize(text: string): string {
|
|
203
|
+
return text.charAt(0).toUpperCase() + text.slice(1);
|
|
204
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// The server-side secret-redaction contract, mirrored for client UX.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Sentinel the server substitutes for secret values before they leave the
|
|
5
|
+
* backend (Cloud environment reads, ChannelApp/OAuthApp reads on both
|
|
6
|
+
* editions). Sending the marker back in an `apply` means "keep the stored
|
|
7
|
+
* secret" — the update pipelines restore the existing encrypted value.
|
|
8
|
+
*/
|
|
9
|
+
export const REDACTED_SECRET_MARKER = "***REDACTED***";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Whether manifest content contains redacted secret values.
|
|
13
|
+
*
|
|
14
|
+
* Used by editing UIs to explain the marker to users ("redacted secrets
|
|
15
|
+
* keep their stored values when applied") instead of letting it read like
|
|
16
|
+
* a bug.
|
|
17
|
+
*/
|
|
18
|
+
export function containsRedactedSecrets(content: string): boolean {
|
|
19
|
+
return content.includes(REDACTED_SECRET_MARKER);
|
|
20
|
+
}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
// Explicit manifest-kind registry: the single place that binds a YAML `kind`
|
|
2
|
+
// to its proto schema and controller RPCs.
|
|
3
|
+
//
|
|
4
|
+
// This is the SDK home of the pattern the CLIs already rely on (Go CLI,
|
|
5
|
+
// TS CLI `resources/apply/handlers.ts`, mcp-server): strict YAML→proto
|
|
6
|
+
// marshalling against the *generated schema*, and apply through the raw
|
|
7
|
+
// command controller with the full resource message. The high-level
|
|
8
|
+
// `*Input` wrappers are intentionally bypassed — they are lossy (they drop
|
|
9
|
+
// `metadata.id` and any field codegen hasn't projected), while the `apply`
|
|
10
|
+
// RPC takes the complete resource and the server upserts by slug.
|
|
11
|
+
//
|
|
12
|
+
// Registration is explicit — to support a new kind, add one entry here.
|
|
13
|
+
|
|
14
|
+
import type { DescMessage, DescService, Message } from "@bufbuild/protobuf";
|
|
15
|
+
import type { Client } from "@connectrpc/connect";
|
|
16
|
+
import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
|
|
17
|
+
import type { ApiResourceReference } from "@stigmer/protos/ai/stigmer/commons/apiresource/io_pb";
|
|
18
|
+
|
|
19
|
+
import { type Agent, AgentSchema } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb";
|
|
20
|
+
import { AgentCommandController } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/command_pb";
|
|
21
|
+
import { AgentQueryController } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/query_pb";
|
|
22
|
+
import { type AgentChannel, AgentChannelSchema } from "@stigmer/protos/ai/stigmer/agentic/agentchannel/v1/api_pb";
|
|
23
|
+
import { AgentChannelCommandController } from "@stigmer/protos/ai/stigmer/agentic/agentchannel/v1/command_pb";
|
|
24
|
+
import { AgentChannelQueryController } from "@stigmer/protos/ai/stigmer/agentic/agentchannel/v1/query_pb";
|
|
25
|
+
import { type AgentInstance, AgentInstanceSchema } from "@stigmer/protos/ai/stigmer/agentic/agentinstance/v1/api_pb";
|
|
26
|
+
import { AgentInstanceCommandController } from "@stigmer/protos/ai/stigmer/agentic/agentinstance/v1/command_pb";
|
|
27
|
+
import { AgentInstanceQueryController } from "@stigmer/protos/ai/stigmer/agentic/agentinstance/v1/query_pb";
|
|
28
|
+
import { type AgentShare, AgentShareSchema } from "@stigmer/protos/ai/stigmer/agentic/agentshare/v1/api_pb";
|
|
29
|
+
import { AgentShareCommandController } from "@stigmer/protos/ai/stigmer/agentic/agentshare/v1/command_pb";
|
|
30
|
+
import { AgentShareQueryController } from "@stigmer/protos/ai/stigmer/agentic/agentshare/v1/query_pb";
|
|
31
|
+
import { type ChannelApp, ChannelAppSchema } from "@stigmer/protos/ai/stigmer/agentic/channelapp/v1/api_pb";
|
|
32
|
+
import { ChannelAppCommandController } from "@stigmer/protos/ai/stigmer/agentic/channelapp/v1/command_pb";
|
|
33
|
+
import { ChannelAppQueryController } from "@stigmer/protos/ai/stigmer/agentic/channelapp/v1/query_pb";
|
|
34
|
+
import { type Datastore, DatastoreSchema } from "@stigmer/protos/ai/stigmer/agentic/datastore/v1/api_pb";
|
|
35
|
+
import { DatastoreCommandController } from "@stigmer/protos/ai/stigmer/agentic/datastore/v1/command_pb";
|
|
36
|
+
import { DatastoreQueryController } from "@stigmer/protos/ai/stigmer/agentic/datastore/v1/query_pb";
|
|
37
|
+
import { type Environment, EnvironmentSchema } from "@stigmer/protos/ai/stigmer/agentic/environment/v1/api_pb";
|
|
38
|
+
import { EnvironmentCommandController } from "@stigmer/protos/ai/stigmer/agentic/environment/v1/command_pb";
|
|
39
|
+
import { EnvironmentQueryController } from "@stigmer/protos/ai/stigmer/agentic/environment/v1/query_pb";
|
|
40
|
+
import { type McpServer, McpServerSchema } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/api_pb";
|
|
41
|
+
import { McpServerCommandController } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/command_pb";
|
|
42
|
+
import { McpServerQueryController } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/query_pb";
|
|
43
|
+
import { type Workflow, WorkflowSchema } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/api_pb";
|
|
44
|
+
import { WorkflowCommandController } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/command_pb";
|
|
45
|
+
import { WorkflowQueryController } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/query_pb";
|
|
46
|
+
import { type IdentityProvider, IdentityProviderSchema } from "@stigmer/protos/ai/stigmer/iam/identityprovider/v1/api_pb";
|
|
47
|
+
import { IdentityProviderCommandController } from "@stigmer/protos/ai/stigmer/iam/identityprovider/v1/command_pb";
|
|
48
|
+
import { IdentityProviderQueryController } from "@stigmer/protos/ai/stigmer/iam/identityprovider/v1/query_pb";
|
|
49
|
+
import { type OAuthApp, OAuthAppSchema } from "@stigmer/protos/ai/stigmer/iam/oauthapp/v1/api_pb";
|
|
50
|
+
import { OAuthAppCommandController } from "@stigmer/protos/ai/stigmer/iam/oauthapp/v1/command_pb";
|
|
51
|
+
import { OAuthAppQueryController } from "@stigmer/protos/ai/stigmer/iam/oauthapp/v1/query_pb";
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Accessor for a raw Connect client over a generated service controller.
|
|
55
|
+
* Implementations cache clients per service (see `ManifestClient`).
|
|
56
|
+
*/
|
|
57
|
+
export type ServiceClientFn = <Desc extends DescService>(service: Desc) => Client<Desc>;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Binds one YAML `kind` to everything the manifest engine needs:
|
|
61
|
+
* the proto schema (strict marshalling), the command controller's `apply`
|
|
62
|
+
* RPC, and the query controller's `getByReference` RPC (create-vs-update
|
|
63
|
+
* preview).
|
|
64
|
+
*/
|
|
65
|
+
export interface ManifestKindHandler {
|
|
66
|
+
/** The platform resource kind enum value. */
|
|
67
|
+
readonly kind: ApiResourceKind;
|
|
68
|
+
/** The YAML discriminator, e.g. `"Agent"`. */
|
|
69
|
+
readonly yamlKind: string;
|
|
70
|
+
/** Human-facing name for messages, e.g. `"MCP Server"`. */
|
|
71
|
+
readonly displayName: string;
|
|
72
|
+
/** Canonical `apiVersion` for this kind, e.g. `"agentic.stigmer.ai/v1"`. */
|
|
73
|
+
readonly apiVersion: string;
|
|
74
|
+
/** Proto schema for strict YAML↔proto conversion. */
|
|
75
|
+
readonly schema: DescMessage;
|
|
76
|
+
/**
|
|
77
|
+
* Position in the dependency apply order (ascending). Referenced kinds
|
|
78
|
+
* apply before their dependents, e.g. an McpServer before the Agent that
|
|
79
|
+
* uses it, and everything an AgentChannel references before the channel.
|
|
80
|
+
*/
|
|
81
|
+
readonly applyOrder: number;
|
|
82
|
+
/** Drive the command controller's `apply` RPC with the full resource. */
|
|
83
|
+
apply(clientFor: ServiceClientFn, message: Message): Promise<Message>;
|
|
84
|
+
/** Load the current server state by org/slug reference. */
|
|
85
|
+
getByReference(clientFor: ServiceClientFn, ref: ApiResourceReference): Promise<Message>;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const AGENTIC_V1 = "agentic.stigmer.ai/v1";
|
|
89
|
+
const IAM_V1 = "iam.stigmer.ai/v1";
|
|
90
|
+
|
|
91
|
+
const HANDLERS: readonly ManifestKindHandler[] = [
|
|
92
|
+
{
|
|
93
|
+
kind: ApiResourceKind.mcp_server,
|
|
94
|
+
yamlKind: "McpServer",
|
|
95
|
+
displayName: "MCP Server",
|
|
96
|
+
apiVersion: AGENTIC_V1,
|
|
97
|
+
schema: McpServerSchema,
|
|
98
|
+
applyOrder: 1,
|
|
99
|
+
apply: (c, m) => c(McpServerCommandController).apply(m as McpServer),
|
|
100
|
+
getByReference: (c, ref) => c(McpServerQueryController).getByReference(ref),
|
|
101
|
+
},
|
|
102
|
+
// A Datastore applies before the Agents that reference it via
|
|
103
|
+
// datastore_usages (the McpServer-before-Agent pattern).
|
|
104
|
+
{
|
|
105
|
+
kind: ApiResourceKind.datastore,
|
|
106
|
+
yamlKind: "Datastore",
|
|
107
|
+
displayName: "Datastore",
|
|
108
|
+
apiVersion: AGENTIC_V1,
|
|
109
|
+
schema: DatastoreSchema,
|
|
110
|
+
applyOrder: 2,
|
|
111
|
+
apply: (c, m) => c(DatastoreCommandController).apply(m as Datastore),
|
|
112
|
+
getByReference: (c, ref) => c(DatastoreQueryController).getByReference(ref),
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
kind: ApiResourceKind.agent,
|
|
116
|
+
yamlKind: "Agent",
|
|
117
|
+
displayName: "Agent",
|
|
118
|
+
apiVersion: AGENTIC_V1,
|
|
119
|
+
schema: AgentSchema,
|
|
120
|
+
applyOrder: 3,
|
|
121
|
+
apply: (c, m) => c(AgentCommandController).apply(m as Agent),
|
|
122
|
+
getByReference: (c, ref) => c(AgentQueryController).getByReference(ref),
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
kind: ApiResourceKind.workflow,
|
|
126
|
+
yamlKind: "Workflow",
|
|
127
|
+
displayName: "Workflow",
|
|
128
|
+
apiVersion: AGENTIC_V1,
|
|
129
|
+
schema: WorkflowSchema,
|
|
130
|
+
applyOrder: 4,
|
|
131
|
+
apply: (c, m) => c(WorkflowCommandController).apply(m as Workflow),
|
|
132
|
+
getByReference: (c, ref) => c(WorkflowQueryController).getByReference(ref),
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
kind: ApiResourceKind.environment,
|
|
136
|
+
yamlKind: "Environment",
|
|
137
|
+
displayName: "Environment",
|
|
138
|
+
apiVersion: AGENTIC_V1,
|
|
139
|
+
schema: EnvironmentSchema,
|
|
140
|
+
applyOrder: 5,
|
|
141
|
+
apply: (c, m) => c(EnvironmentCommandController).apply(m as Environment),
|
|
142
|
+
getByReference: (c, ref) => c(EnvironmentQueryController).getByReference(ref),
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
kind: ApiResourceKind.identity_provider,
|
|
146
|
+
yamlKind: "IdentityProvider",
|
|
147
|
+
displayName: "Identity Provider",
|
|
148
|
+
apiVersion: IAM_V1,
|
|
149
|
+
schema: IdentityProviderSchema,
|
|
150
|
+
applyOrder: 6,
|
|
151
|
+
apply: (c, m) => c(IdentityProviderCommandController).apply(m as IdentityProvider),
|
|
152
|
+
getByReference: (c, ref) => c(IdentityProviderQueryController).getByReference(ref),
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
kind: ApiResourceKind.oauth_app,
|
|
156
|
+
yamlKind: "OAuthApp",
|
|
157
|
+
displayName: "OAuth App",
|
|
158
|
+
apiVersion: IAM_V1,
|
|
159
|
+
schema: OAuthAppSchema,
|
|
160
|
+
applyOrder: 7,
|
|
161
|
+
apply: (c, m) => c(OAuthAppCommandController).apply(m as OAuthApp),
|
|
162
|
+
getByReference: (c, ref) => c(OAuthAppQueryController).getByReference(ref),
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
kind: ApiResourceKind.channel_app,
|
|
166
|
+
yamlKind: "ChannelApp",
|
|
167
|
+
displayName: "Channel App",
|
|
168
|
+
apiVersion: AGENTIC_V1,
|
|
169
|
+
schema: ChannelAppSchema,
|
|
170
|
+
applyOrder: 8,
|
|
171
|
+
apply: (c, m) => c(ChannelAppCommandController).apply(m as ChannelApp),
|
|
172
|
+
getByReference: (c, ref) => c(ChannelAppQueryController).getByReference(ref),
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
kind: ApiResourceKind.agent_instance,
|
|
176
|
+
yamlKind: "AgentInstance",
|
|
177
|
+
displayName: "Agent Instance",
|
|
178
|
+
apiVersion: AGENTIC_V1,
|
|
179
|
+
schema: AgentInstanceSchema,
|
|
180
|
+
applyOrder: 9,
|
|
181
|
+
apply: (c, m) => c(AgentInstanceCommandController).apply(m as AgentInstance),
|
|
182
|
+
getByReference: (c, ref) => c(AgentInstanceQueryController).getByReference(ref),
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
kind: ApiResourceKind.agent_share,
|
|
186
|
+
yamlKind: "AgentShare",
|
|
187
|
+
displayName: "Agent Share",
|
|
188
|
+
apiVersion: AGENTIC_V1,
|
|
189
|
+
schema: AgentShareSchema,
|
|
190
|
+
applyOrder: 10,
|
|
191
|
+
apply: (c, m) => c(AgentShareCommandController).apply(m as AgentShare),
|
|
192
|
+
getByReference: (c, ref) => c(AgentShareQueryController).getByReference(ref),
|
|
193
|
+
},
|
|
194
|
+
// Last: an AgentChannel references an Agent, a ChannelApp, and Environments.
|
|
195
|
+
{
|
|
196
|
+
kind: ApiResourceKind.agent_channel,
|
|
197
|
+
yamlKind: "AgentChannel",
|
|
198
|
+
displayName: "Agent Channel",
|
|
199
|
+
apiVersion: AGENTIC_V1,
|
|
200
|
+
schema: AgentChannelSchema,
|
|
201
|
+
applyOrder: 11,
|
|
202
|
+
apply: (c, m) => c(AgentChannelCommandController).apply(m as AgentChannel),
|
|
203
|
+
getByReference: (c, ref) => c(AgentChannelQueryController).getByReference(ref),
|
|
204
|
+
},
|
|
205
|
+
];
|
|
206
|
+
|
|
207
|
+
const BY_YAML_KIND: ReadonlyMap<string, ManifestKindHandler> = new Map(
|
|
208
|
+
HANDLERS.map((h) => [h.yamlKind, h]),
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
const BY_TYPE_NAME: ReadonlyMap<string, ManifestKindHandler> = new Map(
|
|
212
|
+
HANDLERS.map((h) => [h.schema.typeName, h]),
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
/** All kinds the manifest engine supports, in dependency apply order. */
|
|
216
|
+
export function manifestKinds(): readonly ManifestKindHandler[] {
|
|
217
|
+
return HANDLERS;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/** Resolve a handler by its YAML `kind` discriminator (e.g. `"Agent"`). */
|
|
221
|
+
export function manifestHandlerForYamlKind(
|
|
222
|
+
yamlKind: string,
|
|
223
|
+
): ManifestKindHandler | undefined {
|
|
224
|
+
return BY_YAML_KIND.get(yamlKind);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/** Resolve a handler from a proto message's fully-qualified type name. */
|
|
228
|
+
export function manifestHandlerForTypeName(
|
|
229
|
+
typeName: string,
|
|
230
|
+
): ManifestKindHandler | undefined {
|
|
231
|
+
return BY_TYPE_NAME.get(typeName);
|
|
232
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// Proto → canonical Stigmer YAML serialization.
|
|
2
|
+
//
|
|
3
|
+
// Follows the platform-wide protojson parity contract (Go CLI, TS CLI
|
|
4
|
+
// output/proto.ts, mcp-server marshal.ts): `toJson` with
|
|
5
|
+
// `useProtoFieldName: true` emits snake_case field names and omits unset
|
|
6
|
+
// defaults, so the output round-trips through `parseManifest`'s strict
|
|
7
|
+
// `fromJson` without loss.
|
|
8
|
+
//
|
|
9
|
+
// Two deliberate departures from the raw protojson projection, both for
|
|
10
|
+
// the *editable manifest* use case:
|
|
11
|
+
// - `status` and `metadata.version` are stripped — they are system-managed
|
|
12
|
+
// and would be ignored (or rejected) on apply.
|
|
13
|
+
// - the envelope field is spelled `apiVersion` (the canonical form used in
|
|
14
|
+
// every repo manifest and doc); `fromJson` accepts both spellings.
|
|
15
|
+
|
|
16
|
+
import { toJson, type Message } from "@bufbuild/protobuf";
|
|
17
|
+
import { stringify as stringifyYaml } from "yaml";
|
|
18
|
+
import { manifestHandlerForTypeName, manifestKinds } from "./registry.js";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Serialize a resource proto into the canonical, editable Stigmer YAML form.
|
|
22
|
+
*
|
|
23
|
+
* The resource kind is derived from the message's proto type, so any
|
|
24
|
+
* registry-supported resource (an `Agent` from `stigmer.agent.get()`, an
|
|
25
|
+
* `Environment` from `stigmer.environment.get()`, …) serializes with the
|
|
26
|
+
* same call. `metadata.id` is preserved — the output is a full-fidelity
|
|
27
|
+
* representation of the stored resource, minus system-managed state.
|
|
28
|
+
*
|
|
29
|
+
* @param message - A resource proto of a registry-supported kind.
|
|
30
|
+
* @returns YAML text suitable for editing and re-applying.
|
|
31
|
+
* @throws {Error} When the message's kind is not in the manifest registry.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```ts
|
|
35
|
+
* import { serializeManifest } from "@stigmer/sdk";
|
|
36
|
+
*
|
|
37
|
+
* const agent = await stigmer.agent.get(agentId);
|
|
38
|
+
* const yaml = serializeManifest(agent);
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export function serializeManifest(message: Message): string {
|
|
42
|
+
const handler = manifestHandlerForTypeName(message.$typeName);
|
|
43
|
+
if (handler === undefined) {
|
|
44
|
+
const supported = manifestKinds()
|
|
45
|
+
.map((h) => h.yamlKind)
|
|
46
|
+
.join(", ");
|
|
47
|
+
throw new Error(
|
|
48
|
+
`Cannot serialize ${message.$typeName} as a manifest: kind is not ` +
|
|
49
|
+
`in the manifest registry. Supported kinds: ${supported}.`,
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const json = toJson(handler.schema, message, { useProtoFieldName: true });
|
|
54
|
+
if (json === null || typeof json !== "object" || Array.isArray(json)) {
|
|
55
|
+
throw new Error(
|
|
56
|
+
`Cannot serialize ${handler.displayName}: unexpected non-object ` +
|
|
57
|
+
"protojson projection.",
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const { api_version, kind, status, metadata, ...rest } =
|
|
62
|
+
json as Record<string, unknown>;
|
|
63
|
+
void status;
|
|
64
|
+
|
|
65
|
+
const doc: Record<string, unknown> = {
|
|
66
|
+
apiVersion: typeof api_version === "string" && api_version !== ""
|
|
67
|
+
? api_version
|
|
68
|
+
: handler.apiVersion,
|
|
69
|
+
kind: typeof kind === "string" && kind !== "" ? kind : handler.yamlKind,
|
|
70
|
+
...(isPlainObject(metadata) && { metadata: stripSystemMetadata(metadata) }),
|
|
71
|
+
...rest,
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// lineWidth: 0 disables wrapping (long instruction lines stay intact);
|
|
75
|
+
// blockQuote: "literal" renders multi-line strings as readable `|` blocks.
|
|
76
|
+
return stringifyYaml(doc, { lineWidth: 0, blockQuote: "literal" });
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// metadata.version (version id, previous-version pointer, tag) is written by
|
|
80
|
+
// the server on every mutation; round-tripping it through an edit would be
|
|
81
|
+
// misleading at best. Everything else — including id — is user-meaningful.
|
|
82
|
+
function stripSystemMetadata(
|
|
83
|
+
metadata: Record<string, unknown>,
|
|
84
|
+
): Record<string, unknown> {
|
|
85
|
+
const { version, ...rest } = metadata;
|
|
86
|
+
void version;
|
|
87
|
+
return rest;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
91
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
92
|
+
}
|
package/src/session.ts
CHANGED
|
@@ -35,3 +35,51 @@ export function resolvedSubject(subject: string | undefined): string | null {
|
|
|
35
35
|
}
|
|
36
36
|
return subject;
|
|
37
37
|
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* `SessionSpec.metadata` key carrying embedder-supplied session context —
|
|
41
|
+
* standing, per-user context (who the caller is, their experience level,
|
|
42
|
+
* their standing instructions) that the agent runner injects into the
|
|
43
|
+
* system prompt on every turn but the conversation UI never renders
|
|
44
|
+
* (stigmer/stigmer#286).
|
|
45
|
+
*
|
|
46
|
+
* Set it at session creation via `session.create({ metadata })` or the
|
|
47
|
+
* one-call bootstrap's `sessionSpec.metadata`, or use the typed
|
|
48
|
+
* `sessionContext` field on the `@stigmer/react` hooks, which maps onto
|
|
49
|
+
* this key via {@link mergeSessionContext}.
|
|
50
|
+
*
|
|
51
|
+
* Personalization, not authorization: anyone who can create the session
|
|
52
|
+
* can set this value (the same trust level as authoring the first
|
|
53
|
+
* message), so agents treat it as context — never as a credential or a
|
|
54
|
+
* permission grant. It is hidden from the conversation thread, not from
|
|
55
|
+
* the API: `session.get` returns the full spec including this key, so it
|
|
56
|
+
* is not a secrets channel — secrets belong in `runtimeEnv` or
|
|
57
|
+
* Environment resources.
|
|
58
|
+
*
|
|
59
|
+
* Pinned verbatim to `SESSION_CONTEXT_METADATA_KEY` in the runner
|
|
60
|
+
* (`backend/services/runner/src/shared/session-context.ts`), with mirror
|
|
61
|
+
* guard tests on both sides — a drift degrades to the agent simply not
|
|
62
|
+
* receiving the context, never worse.
|
|
63
|
+
*/
|
|
64
|
+
export const SESSION_CONTEXT_METADATA_KEY = "stigmer.ai/session-context";
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Fold a typed session-context value into a session's spec metadata map,
|
|
68
|
+
* under {@link SESSION_CONTEXT_METADATA_KEY}.
|
|
69
|
+
*
|
|
70
|
+
* The single owner of the precedence rule: a non-blank `sessionContext`
|
|
71
|
+
* wins over any value already present under the reserved key in
|
|
72
|
+
* `metadata`. A blank/undefined `sessionContext` leaves `metadata`
|
|
73
|
+
* untouched (returned as-is, possibly `undefined` — callers never send
|
|
74
|
+
* an empty map).
|
|
75
|
+
*/
|
|
76
|
+
export function mergeSessionContext(
|
|
77
|
+
metadata: Record<string, string> | undefined,
|
|
78
|
+
sessionContext: string | undefined,
|
|
79
|
+
): Record<string, string> | undefined {
|
|
80
|
+
const context = sessionContext?.trim();
|
|
81
|
+
if (!context) {
|
|
82
|
+
return metadata;
|
|
83
|
+
}
|
|
84
|
+
return { ...metadata, [SESSION_CONTEXT_METADATA_KEY]: context };
|
|
85
|
+
}
|
package/src/stigmer.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { ActivityClient } from "./activity.js";
|
|
2
2
|
import { BillingClient } from "./billing.js";
|
|
3
|
+
import { CursorAccountsClient } from "./cursor-accounts.js";
|
|
3
4
|
import { GeneratedClient } from "./gen/client.js";
|
|
4
5
|
import { GitHubClient } from "./github.js";
|
|
6
|
+
import { ManifestClient } from "./manifest/index.js";
|
|
5
7
|
import { PlatformClient } from "./platform.js";
|
|
6
8
|
import { SearchClient } from "./search.js";
|
|
7
9
|
import { createStigmerTransport } from "./transport.js";
|
|
@@ -27,6 +29,7 @@ type ExecutionTargetOption = StigmerConfig["executionTarget"];
|
|
|
27
29
|
* - {@link billing} credit management and Stripe integration client
|
|
28
30
|
* - Cross-resource {@link search} client
|
|
29
31
|
* - {@link github} OAuth integration client
|
|
32
|
+
* - Kind-agnostic {@link manifest} client for declarative YAML apply
|
|
30
33
|
*
|
|
31
34
|
* @example
|
|
32
35
|
* ```typescript
|
|
@@ -73,9 +76,12 @@ export class Stigmer extends GeneratedClient {
|
|
|
73
76
|
|
|
74
77
|
readonly activity: ActivityClient;
|
|
75
78
|
readonly billing: BillingClient;
|
|
79
|
+
/** Managed Cursor accounts (platform operators only). */
|
|
80
|
+
readonly cursorAccounts: CursorAccountsClient;
|
|
76
81
|
readonly platform: PlatformClient;
|
|
77
82
|
readonly search: SearchClient;
|
|
78
83
|
readonly github: GitHubClient;
|
|
84
|
+
readonly manifest: ManifestClient;
|
|
79
85
|
|
|
80
86
|
private readonly _tokenProvider: TokenProvider;
|
|
81
87
|
|
|
@@ -94,9 +100,11 @@ export class Stigmer extends GeneratedClient {
|
|
|
94
100
|
|
|
95
101
|
this.activity = new ActivityClient(transport);
|
|
96
102
|
this.billing = new BillingClient(transport);
|
|
103
|
+
this.cursorAccounts = new CursorAccountsClient(transport);
|
|
97
104
|
this.platform = new PlatformClient(transport);
|
|
98
105
|
this.search = new SearchClient(transport);
|
|
99
106
|
this.github = new GitHubClient(transport);
|
|
107
|
+
this.manifest = new ManifestClient(transport);
|
|
100
108
|
|
|
101
109
|
if (this.defaultExecutionTarget != null) {
|
|
102
110
|
this._applyExecutionTargetDefaults();
|
package/stigmer.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { ActivityClient } from "./activity.js";
|
|
2
2
|
import { BillingClient } from "./billing.js";
|
|
3
|
+
import { CursorAccountsClient } from "./cursor-accounts.js";
|
|
3
4
|
import { GeneratedClient } from "./gen/client.js";
|
|
4
5
|
import { GitHubClient } from "./github.js";
|
|
6
|
+
import { ManifestClient } from "./manifest/index.js";
|
|
5
7
|
import { PlatformClient } from "./platform.js";
|
|
6
8
|
import { SearchClient } from "./search.js";
|
|
7
9
|
import { type StigmerConfig } from "./config.js";
|
|
@@ -19,6 +21,7 @@ import { ExecutionTarget } from "@stigmer/protos/ai/stigmer/agentic/session/v1/e
|
|
|
19
21
|
* - {@link billing} credit management and Stripe integration client
|
|
20
22
|
* - Cross-resource {@link search} client
|
|
21
23
|
* - {@link github} OAuth integration client
|
|
24
|
+
* - Kind-agnostic {@link manifest} client for declarative YAML apply
|
|
22
25
|
*
|
|
23
26
|
* @example
|
|
24
27
|
* ```typescript
|
|
@@ -62,9 +65,12 @@ export declare class Stigmer extends GeneratedClient {
|
|
|
62
65
|
readonly defaultExecutionTarget: ExecutionTarget | undefined;
|
|
63
66
|
readonly activity: ActivityClient;
|
|
64
67
|
readonly billing: BillingClient;
|
|
68
|
+
/** Managed Cursor accounts (platform operators only). */
|
|
69
|
+
readonly cursorAccounts: CursorAccountsClient;
|
|
65
70
|
readonly platform: PlatformClient;
|
|
66
71
|
readonly search: SearchClient;
|
|
67
72
|
readonly github: GitHubClient;
|
|
73
|
+
readonly manifest: ManifestClient;
|
|
68
74
|
private readonly _tokenProvider;
|
|
69
75
|
constructor(config: StigmerConfig);
|
|
70
76
|
/**
|