@stigmer/sdk 3.12.2 → 3.12.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/__tests__/billing.test.js +36 -0
- package/__tests__/billing.test.js.map +1 -1
- package/__tests__/skill.test.d.ts +2 -0
- package/__tests__/skill.test.d.ts.map +1 -0
- package/__tests__/skill.test.js +117 -0
- package/__tests__/skill.test.js.map +1 -0
- package/billing.d.ts +27 -0
- package/billing.d.ts.map +1 -1
- package/billing.js +26 -1
- package/billing.js.map +1 -1
- package/errors.d.ts +1 -1
- package/errors.d.ts.map +1 -1
- package/errors.js +1 -1
- package/errors.js.map +1 -1
- package/execution/__tests__/tool-view.fixtures.test.js +23 -4
- package/execution/__tests__/tool-view.fixtures.test.js.map +1 -1
- package/execution/tool-view.d.ts +32 -0
- package/execution/tool-view.d.ts.map +1 -1
- package/execution/tool-view.js +45 -0
- package/execution/tool-view.js.map +1 -1
- package/gen/client.d.ts +1 -1
- package/gen/client.d.ts.map +1 -1
- package/gen/client.js +1 -1
- package/gen/client.js.map +1 -1
- package/gen/errors.d.ts +7 -0
- package/gen/errors.d.ts.map +1 -1
- package/gen/errors.js +9 -0
- package/gen/errors.js.map +1 -1
- package/gen/skill.d.ts +3 -1
- package/gen/skill.d.ts.map +1 -1
- package/gen/skill.js +16 -0
- package/gen/skill.js.map +1 -1
- package/index.d.ts +4 -2
- package/index.d.ts.map +1 -1
- package/index.js +5 -2
- package/index.js.map +1 -1
- package/package.json +2 -2
- package/provider-standing.d.ts +19 -0
- package/provider-standing.d.ts.map +1 -0
- package/provider-standing.js +31 -0
- package/provider-standing.js.map +1 -0
- package/skill.d.ts +51 -0
- package/skill.d.ts.map +1 -0
- package/skill.js +102 -0
- package/skill.js.map +1 -0
- package/src/__tests__/billing.test.ts +44 -0
- package/src/__tests__/skill.test.ts +150 -0
- package/src/billing.ts +44 -0
- package/src/errors.ts +1 -0
- package/src/execution/__tests__/tool-view.fixtures.test.ts +41 -4
- package/src/execution/tool-view.ts +52 -0
- package/src/gen/client.ts +1 -1
- package/src/gen/errors.ts +10 -0
- package/src/gen/skill.ts +13 -1
- package/src/index.ts +8 -0
- package/src/provider-standing.ts +37 -0
- package/src/skill.ts +125 -0
- package/src/stigmer.ts +12 -0
- package/stigmer.d.ts +10 -0
- package/stigmer.d.ts.map +1 -1
- package/stigmer.js +12 -0
- package/stigmer.js.map +1 -1
package/skill.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { create } from "@bufbuild/protobuf";
|
|
2
|
+
import { Code } from "@connectrpc/connect";
|
|
3
|
+
import { CreateSkillArtifactUploadUrlRequestSchema, PushSkillRequestSchema, } from "@stigmer/protos/ai/stigmer/agentic/skill/v1/io_pb";
|
|
4
|
+
import { isUnimplemented, StigmerError } from "./gen/errors.js";
|
|
5
|
+
import { SkillClient } from "./gen/skill.js";
|
|
6
|
+
/**
|
|
7
|
+
* Largest artifact pushed inline in the gRPC request (#675). The server's
|
|
8
|
+
* transport cap is 10MB for the WHOLE message, so the artifact leaves 64KB
|
|
9
|
+
* of headroom for the request envelope (org, tag, provenance, framing).
|
|
10
|
+
* Mirrors the Go SDK's maxInlineArtifactBytes.
|
|
11
|
+
*/
|
|
12
|
+
export const MAX_INLINE_ARTIFACT_BYTES = 10 * 1024 * 1024 - 64 * 1024;
|
|
13
|
+
/**
|
|
14
|
+
* Skill client with transport-aware push routing (stigmer#675 / #701).
|
|
15
|
+
*
|
|
16
|
+
* The gRPC transport caps messages at 10MB while skills may be up to 100MB,
|
|
17
|
+
* so `push` routes by size: small artifacts travel inline in the request
|
|
18
|
+
* (one round trip, unchanged behavior), larger ones are staged over HTTP
|
|
19
|
+
* via `createArtifactUploadUrl` — a capability URL, so no auth header —
|
|
20
|
+
* and pushed by reference. Callers never see the mechanics: `push(req)`
|
|
21
|
+
* simply works for any valid skill size.
|
|
22
|
+
*
|
|
23
|
+
* Every other method is the generated client's, inherited unchanged.
|
|
24
|
+
*/
|
|
25
|
+
export class RoutedSkillClient extends SkillClient {
|
|
26
|
+
fetchImpl;
|
|
27
|
+
/**
|
|
28
|
+
* @param fetchImpl - Custom `fetch` for the staging PUT. Must be the same
|
|
29
|
+
* implementation the transport uses where the global one is restricted
|
|
30
|
+
* (the Tauri CSP/CORS case the `Stigmer.fetch` property documents).
|
|
31
|
+
*/
|
|
32
|
+
constructor(transport, fetchImpl) {
|
|
33
|
+
super(transport);
|
|
34
|
+
this.fetchImpl = fetchImpl;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Push a skill, routing the artifact by size (see the class comment).
|
|
38
|
+
*
|
|
39
|
+
* A request that already carries an `artifactUploadRef` is passed through
|
|
40
|
+
* untouched — the caller has done its own staging.
|
|
41
|
+
*/
|
|
42
|
+
async push(input) {
|
|
43
|
+
if (input.artifactUploadRef !== "" || input.artifact.length <= MAX_INLINE_ARTIFACT_BYTES) {
|
|
44
|
+
return super.push(input);
|
|
45
|
+
}
|
|
46
|
+
return this.pushViaUploadUrl(input);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Stage the artifact over HTTP and push by reference:
|
|
50
|
+
* createArtifactUploadUrl → PUT bytes → push(artifactUploadRef).
|
|
51
|
+
*/
|
|
52
|
+
async pushViaUploadUrl(input) {
|
|
53
|
+
let minted;
|
|
54
|
+
try {
|
|
55
|
+
minted = await super.createArtifactUploadUrl(create(CreateSkillArtifactUploadUrlRequestSchema, {
|
|
56
|
+
org: input.org,
|
|
57
|
+
sizeBytes: BigInt(input.artifact.length),
|
|
58
|
+
}));
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
if (isUnimplemented(err)) {
|
|
62
|
+
// Pre-transfer-lane server: without staging, an artifact this size
|
|
63
|
+
// physically cannot travel. Say so instead of surfacing the raw
|
|
64
|
+
// transport error (the failure mode #675 reported).
|
|
65
|
+
throw new StigmerError("unknown", `skill artifact is ${input.artifact.length} bytes, above the ~10MB gRPC message cap, ` +
|
|
66
|
+
"and this server does not support the HTTP artifact transfer lane — " +
|
|
67
|
+
"upgrade stigmer-server to push skills of this size", Code.Unimplemented, { cause: err });
|
|
68
|
+
}
|
|
69
|
+
throw err;
|
|
70
|
+
}
|
|
71
|
+
await this.putArtifact(minted.url, input.artifact);
|
|
72
|
+
// Same request, artifact traveling by reference instead of by value.
|
|
73
|
+
const byRef = create(PushSkillRequestSchema, {
|
|
74
|
+
...input,
|
|
75
|
+
artifact: new Uint8Array(0),
|
|
76
|
+
artifactUploadRef: minted.artifactUploadRef,
|
|
77
|
+
});
|
|
78
|
+
return super.push(byRef);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* PUT the artifact ZIP to the staging URL. The URL is the credential
|
|
82
|
+
* (capability semantics — a pre-signed R2 URL on cloud, the server's own
|
|
83
|
+
* transfer lane on OSS), so no auth header is attached.
|
|
84
|
+
*/
|
|
85
|
+
async putArtifact(url, artifact) {
|
|
86
|
+
const doFetch = this.fetchImpl ?? globalThis.fetch;
|
|
87
|
+
const resp = await doFetch(url, {
|
|
88
|
+
method: "PUT",
|
|
89
|
+
// Both DOM and undici accept an ArrayBufferView body at runtime; the
|
|
90
|
+
// cast bridges TS 5.7's ArrayBufferLike generic, which BodyInit's
|
|
91
|
+
// typing predates. A Blob/Buffer wrapper would copy up to 100MB for
|
|
92
|
+
// nothing, and Buffer is Node-only while this client is isomorphic.
|
|
93
|
+
body: artifact,
|
|
94
|
+
headers: { "content-type": "application/zip" },
|
|
95
|
+
});
|
|
96
|
+
if (!resp.ok) {
|
|
97
|
+
const detail = (await resp.text().catch(() => "")).slice(0, 512).trim();
|
|
98
|
+
throw new StigmerError("unknown", `skill artifact upload rejected with HTTP ${resp.status}${detail === "" ? "" : `: ${detail}`}`, Code.Unknown);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=skill.js.map
|
package/skill.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skill.js","sourceRoot":"","sources":["../src/skill.ts"],"names":[],"mappings":"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"}
|
|
@@ -12,6 +12,7 @@ import { BillingQueryController } from "@stigmer/protos/ai/stigmer/billing/v1/qu
|
|
|
12
12
|
import {
|
|
13
13
|
CreditLedgerResponseSchema,
|
|
14
14
|
type AdjustCreditsInput,
|
|
15
|
+
type GrantCreditsInput,
|
|
15
16
|
type GetCreditLedgerInput,
|
|
16
17
|
} from "@stigmer/protos/ai/stigmer/billing/v1/io_pb";
|
|
17
18
|
import { CreditLedgerEntrySchema } from "@stigmer/protos/ai/stigmer/billing/v1/credit_pb";
|
|
@@ -20,6 +21,7 @@ import { BillingClient } from "../billing.js";
|
|
|
20
21
|
|
|
21
22
|
interface Captured {
|
|
22
23
|
adjustCredits?: AdjustCreditsInput;
|
|
24
|
+
grantCredits?: GrantCreditsInput;
|
|
23
25
|
getCreditLedger?: GetCreditLedgerInput;
|
|
24
26
|
}
|
|
25
27
|
|
|
@@ -30,6 +32,10 @@ function fakeTransport(captured: Captured): Transport {
|
|
|
30
32
|
captured.adjustCredits = req;
|
|
31
33
|
return create(CreditLedgerEntrySchema, { amountMicros: req.amountMicros });
|
|
32
34
|
},
|
|
35
|
+
grantCredits: (req) => {
|
|
36
|
+
captured.grantCredits = req;
|
|
37
|
+
return create(CreditLedgerEntrySchema, { amountMicros: req.amountMicros });
|
|
38
|
+
},
|
|
33
39
|
});
|
|
34
40
|
service(BillingQueryController, {
|
|
35
41
|
getCreditLedger: (req) => {
|
|
@@ -60,6 +66,44 @@ describe("BillingClient.adjustCredits", () => {
|
|
|
60
66
|
});
|
|
61
67
|
});
|
|
62
68
|
|
|
69
|
+
describe("BillingClient.grantCredits", () => {
|
|
70
|
+
it("maps params including the expiry onto GrantCreditsInput", async () => {
|
|
71
|
+
const captured: Captured = {};
|
|
72
|
+
const client = new BillingClient(fakeTransport(captured));
|
|
73
|
+
|
|
74
|
+
const expiresAt = new Date("2026-08-31T23:59:59Z");
|
|
75
|
+
const entry = await client.grantCredits({
|
|
76
|
+
orgId: "acme",
|
|
77
|
+
amountMicros: 5_000_000n,
|
|
78
|
+
expiresAt,
|
|
79
|
+
reason: "monthly free allowance 2026-08",
|
|
80
|
+
idempotencyKey: "allowance-acme-2026-08",
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
expect(entry.amountMicros).toBe(5_000_000n);
|
|
84
|
+
const req = captured.grantCredits;
|
|
85
|
+
expect(req?.orgId).toBe("acme");
|
|
86
|
+
expect(req?.amountMicros).toBe(5_000_000n);
|
|
87
|
+
expect(req?.expiresAt && timestampDate(req.expiresAt)).toEqual(expiresAt);
|
|
88
|
+
expect(req?.reason).toBe("monthly free allowance 2026-08");
|
|
89
|
+
expect(req?.idempotencyKey).toBe("allowance-acme-2026-08");
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("omits the expiry for a never-expiring grant", async () => {
|
|
93
|
+
const captured: Captured = {};
|
|
94
|
+
const client = new BillingClient(fakeTransport(captured));
|
|
95
|
+
|
|
96
|
+
await client.grantCredits({
|
|
97
|
+
orgId: "acme",
|
|
98
|
+
amountMicros: 1_000_000n,
|
|
99
|
+
reason: "welcome credit",
|
|
100
|
+
idempotencyKey: "welcome-acme",
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
expect(captured.grantCredits?.expiresAt).toBeUndefined();
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
63
107
|
describe("BillingClient.getCreditLedger", () => {
|
|
64
108
|
it("maps all filters including the time range", async () => {
|
|
65
109
|
const captured: Captured = {};
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
// Wire-shape tests for RoutedSkillClient's size-routed push (stigmer#701).
|
|
2
|
+
// A router transport fakes the skill service in-process, so the routing is
|
|
3
|
+
// exercised through the REAL generated client and its error wrapping — the
|
|
4
|
+
// CLI's earlier copy of this logic keyed its fallback on `instanceof
|
|
5
|
+
// ConnectError`, which the SDK's wrapError made unreachable; these pins run
|
|
6
|
+
// the wrapped path. Mirrors sdk/go/skill_test.go's five routing pins.
|
|
7
|
+
import { describe, expect, it, vi } from "vitest";
|
|
8
|
+
import { Code, ConnectError, createRouterTransport, type Transport } from "@connectrpc/connect";
|
|
9
|
+
import { create } from "@bufbuild/protobuf";
|
|
10
|
+
import { SkillCommandController } from "@stigmer/protos/ai/stigmer/agentic/skill/v1/command_pb";
|
|
11
|
+
import { SkillSchema } from "@stigmer/protos/ai/stigmer/agentic/skill/v1/api_pb";
|
|
12
|
+
import {
|
|
13
|
+
PushSkillRequestSchema,
|
|
14
|
+
SkillArtifactUploadUrlSchema,
|
|
15
|
+
type PushSkillRequest,
|
|
16
|
+
} from "@stigmer/protos/ai/stigmer/agentic/skill/v1/io_pb";
|
|
17
|
+
import { StigmerError } from "../gen/errors.js";
|
|
18
|
+
import { MAX_INLINE_ARTIFACT_BYTES, RoutedSkillClient } from "../skill.js";
|
|
19
|
+
|
|
20
|
+
const STAGING_URL = "http://stage.example/v1/skill-artifacts/uploads/sau_t";
|
|
21
|
+
|
|
22
|
+
interface Captured {
|
|
23
|
+
pushes: PushSkillRequest[];
|
|
24
|
+
mints: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* In-process skill service. `withMint: false` leaves createArtifactUploadUrl
|
|
29
|
+
* unregistered, so the router answers Unimplemented — the exact wire shape
|
|
30
|
+
* of a pre-transfer-lane server.
|
|
31
|
+
*/
|
|
32
|
+
function fakeTransport(captured: Captured, opts: { withMint: boolean } = { withMint: true }): Transport {
|
|
33
|
+
return createRouterTransport(({ service }) => {
|
|
34
|
+
service(SkillCommandController, {
|
|
35
|
+
push: (req) => {
|
|
36
|
+
captured.pushes.push(req);
|
|
37
|
+
return create(SkillSchema, {});
|
|
38
|
+
},
|
|
39
|
+
...(opts.withMint
|
|
40
|
+
? {
|
|
41
|
+
createArtifactUploadUrl: (req) => {
|
|
42
|
+
captured.mints++;
|
|
43
|
+
expect(req.sizeBytes).toBeGreaterThan(0n);
|
|
44
|
+
return create(SkillArtifactUploadUrlSchema, {
|
|
45
|
+
url: STAGING_URL,
|
|
46
|
+
artifactUploadRef: "sau_t",
|
|
47
|
+
ttlSeconds: 900,
|
|
48
|
+
});
|
|
49
|
+
},
|
|
50
|
+
}
|
|
51
|
+
: {}),
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function okFetch(record: { putUrl?: string; putBytes?: number; contentType?: string }) {
|
|
57
|
+
return vi.fn(async (url: any, init: any) => {
|
|
58
|
+
record.putUrl = String(url);
|
|
59
|
+
record.putBytes = init.body instanceof Uint8Array ? init.body.byteLength : -1;
|
|
60
|
+
record.contentType = init.headers["content-type"];
|
|
61
|
+
return new Response(null, { status: 200 });
|
|
62
|
+
}) as unknown as typeof globalThis.fetch;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
describe("RoutedSkillClient.push size routing", () => {
|
|
66
|
+
it("keeps small artifacts inline — no mint, bytes in the request", async () => {
|
|
67
|
+
const captured: Captured = { pushes: [], mints: 0 };
|
|
68
|
+
const client = new RoutedSkillClient(fakeTransport(captured));
|
|
69
|
+
|
|
70
|
+
await client.push(create(PushSkillRequestSchema, { org: "acme", artifact: new Uint8Array(1024) }));
|
|
71
|
+
|
|
72
|
+
expect(captured.mints).toBe(0);
|
|
73
|
+
expect(captured.pushes).toHaveLength(1);
|
|
74
|
+
expect(captured.pushes[0].artifact.length).toBe(1024);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("stages large artifacts over HTTP and pushes by reference, preserving the envelope", async () => {
|
|
78
|
+
const captured: Captured = { pushes: [], mints: 0 };
|
|
79
|
+
const record: { putUrl?: string; putBytes?: number; contentType?: string } = {};
|
|
80
|
+
const client = new RoutedSkillClient(fakeTransport(captured), okFetch(record));
|
|
81
|
+
const artifact = new Uint8Array(MAX_INLINE_ARTIFACT_BYTES + 1);
|
|
82
|
+
|
|
83
|
+
await client.push(create(PushSkillRequestSchema, { org: "acme", artifact, tag: "stable", message: "big" }));
|
|
84
|
+
|
|
85
|
+
expect(captured.mints).toBe(1);
|
|
86
|
+
expect(record.putUrl).toBe(STAGING_URL);
|
|
87
|
+
expect(record.putBytes).toBe(artifact.length);
|
|
88
|
+
expect(record.contentType).toBe("application/zip");
|
|
89
|
+
expect(captured.pushes).toHaveLength(1);
|
|
90
|
+
expect(captured.pushes[0].artifact.length).toBe(0);
|
|
91
|
+
expect(captured.pushes[0].artifactUploadRef).toBe("sau_t");
|
|
92
|
+
// The by-ref rewrite must not lose the rest of the request.
|
|
93
|
+
expect(captured.pushes[0].tag).toBe("stable");
|
|
94
|
+
expect(captured.pushes[0].message).toBe("big");
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("passes an explicit upload ref through untouched — the caller staged it", async () => {
|
|
98
|
+
const captured: Captured = { pushes: [], mints: 0 };
|
|
99
|
+
const client = new RoutedSkillClient(fakeTransport(captured));
|
|
100
|
+
|
|
101
|
+
await client.push(create(PushSkillRequestSchema, { org: "acme", artifactUploadRef: "sau_mine" }));
|
|
102
|
+
|
|
103
|
+
expect(captured.mints).toBe(0);
|
|
104
|
+
expect(captured.pushes).toHaveLength(1);
|
|
105
|
+
expect(captured.pushes[0].artifactUploadRef).toBe("sau_mine");
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("fails loud against servers that predate the transfer lane (wrapped Unimplemented)", async () => {
|
|
109
|
+
const captured: Captured = { pushes: [], mints: 0 };
|
|
110
|
+
const client = new RoutedSkillClient(fakeTransport(captured, { withMint: false }));
|
|
111
|
+
|
|
112
|
+
await expect(
|
|
113
|
+
client.push(create(PushSkillRequestSchema, { org: "acme", artifact: new Uint8Array(MAX_INLINE_ARTIFACT_BYTES + 1) })),
|
|
114
|
+
).rejects.toThrow(/upgrade stigmer-server/);
|
|
115
|
+
expect(captured.pushes).toHaveLength(0);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("surfaces the staging rejection body and never proceeds to push", async () => {
|
|
119
|
+
const captured: Captured = { pushes: [], mints: 0 };
|
|
120
|
+
const rejectingFetch = (async () =>
|
|
121
|
+
new Response("staging slot expired", { status: 404 })) as unknown as typeof globalThis.fetch;
|
|
122
|
+
const client = new RoutedSkillClient(fakeTransport(captured), rejectingFetch);
|
|
123
|
+
|
|
124
|
+
await expect(
|
|
125
|
+
client.push(create(PushSkillRequestSchema, { org: "acme", artifact: new Uint8Array(MAX_INLINE_ARTIFACT_BYTES + 1) })),
|
|
126
|
+
).rejects.toThrow(/HTTP 404: staging slot expired/);
|
|
127
|
+
expect(captured.pushes).toHaveLength(0);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("propagates non-Unimplemented mint errors unmasked", async () => {
|
|
131
|
+
const captured: Captured = { pushes: [], mints: 0 };
|
|
132
|
+
const transport = createRouterTransport(({ service }) => {
|
|
133
|
+
service(SkillCommandController, {
|
|
134
|
+
push: (req) => {
|
|
135
|
+
captured.pushes.push(req);
|
|
136
|
+
return create(SkillSchema, {});
|
|
137
|
+
},
|
|
138
|
+
createArtifactUploadUrl: () => {
|
|
139
|
+
throw new ConnectError("artifact too large", Code.InvalidArgument);
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
const client = new RoutedSkillClient(transport);
|
|
144
|
+
|
|
145
|
+
await expect(
|
|
146
|
+
client.push(create(PushSkillRequestSchema, { org: "acme", artifact: new Uint8Array(MAX_INLINE_ARTIFACT_BYTES + 1) })),
|
|
147
|
+
).rejects.toSatisfy((err: unknown) => err instanceof StigmerError && err.code === "invalid-argument");
|
|
148
|
+
expect(captured.pushes).toHaveLength(0);
|
|
149
|
+
});
|
|
150
|
+
});
|
package/src/billing.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
GetBillingAccountInputSchema,
|
|
8
8
|
GetCreditBalanceInputSchema,
|
|
9
9
|
AdjustCreditsInputSchema,
|
|
10
|
+
GrantCreditsInputSchema,
|
|
10
11
|
GetCreditLedgerInputSchema,
|
|
11
12
|
CreateCreditCheckoutSessionInputSchema,
|
|
12
13
|
CreateBillingPortalSessionInputSchema,
|
|
@@ -69,6 +70,22 @@ export interface AdjustCreditsParams {
|
|
|
69
70
|
readonly idempotencyKey: string;
|
|
70
71
|
}
|
|
71
72
|
|
|
73
|
+
/** Parameters for a promotional credit grant. */
|
|
74
|
+
export interface GrantCreditsParams {
|
|
75
|
+
readonly orgId: string;
|
|
76
|
+
/** Amount to grant. Must be positive; grants never remove credits. */
|
|
77
|
+
readonly amountMicros: bigint;
|
|
78
|
+
/**
|
|
79
|
+
* When the grant expires (use-it-or-lose-it). Omit for a grant that never
|
|
80
|
+
* expires. Precision is whole seconds; sub-second precision is discarded.
|
|
81
|
+
*/
|
|
82
|
+
readonly expiresAt?: Date;
|
|
83
|
+
/** Human-readable reason recorded on the ledger entry (audit trail). */
|
|
84
|
+
readonly reason: string;
|
|
85
|
+
/** Client-supplied deduplication key to prevent double-processing. */
|
|
86
|
+
readonly idempotencyKey: string;
|
|
87
|
+
}
|
|
88
|
+
|
|
72
89
|
/** Parameters for querying the credit ledger. */
|
|
73
90
|
export interface GetCreditLedgerParams {
|
|
74
91
|
readonly orgId: string;
|
|
@@ -222,6 +239,33 @@ export class BillingClient {
|
|
|
222
239
|
}
|
|
223
240
|
}
|
|
224
241
|
|
|
242
|
+
/**
|
|
243
|
+
* Grant promotional credits to an organization, optionally expiring
|
|
244
|
+
* (use-it-or-lose-it).
|
|
245
|
+
*
|
|
246
|
+
* The grant burns before adjustment and purchased credits. When
|
|
247
|
+
* `expiresAt` is set, any remainder unconsumed at that time is removed
|
|
248
|
+
* from the balance by the platform's grant-expiry sweep. Idempotent:
|
|
249
|
+
* replaying an applied idempotency key returns the original ledger entry,
|
|
250
|
+
* even after the expiry has passed. Requires `can_manage_billing` on
|
|
251
|
+
* the org.
|
|
252
|
+
*/
|
|
253
|
+
async grantCredits(params: GrantCreditsParams): Promise<CreditLedgerEntry> {
|
|
254
|
+
try {
|
|
255
|
+
return await this.command.grantCredits(
|
|
256
|
+
create(GrantCreditsInputSchema, {
|
|
257
|
+
orgId: params.orgId,
|
|
258
|
+
amountMicros: params.amountMicros,
|
|
259
|
+
reason: params.reason,
|
|
260
|
+
idempotencyKey: params.idempotencyKey,
|
|
261
|
+
...(params.expiresAt && { expiresAt: timestampFromDate(params.expiresAt) }),
|
|
262
|
+
}),
|
|
263
|
+
);
|
|
264
|
+
} catch (e) {
|
|
265
|
+
throw wrapError(e);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
225
269
|
/** Retrieve paginated credit ledger entries with optional filters. */
|
|
226
270
|
async getCreditLedger(params: GetCreditLedgerParams): Promise<CreditLedgerResponse> {
|
|
227
271
|
try {
|
package/src/errors.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
// Validates the tool-view layer against the shared cross-
|
|
2
|
-
// test/fixtures/tool-view/. The
|
|
3
|
-
//
|
|
1
|
+
// Validates the tool-view layer against the shared cross-surface contract in
|
|
2
|
+
// test/fixtures/tool-view/. The runner's middleware tests assert the same
|
|
3
|
+
// fixtures from the writer side, so producers and this reader cannot drift in
|
|
4
|
+
// classification, result interpretation, or intent extraction.
|
|
4
5
|
|
|
5
6
|
import { readFileSync } from "node:fs";
|
|
6
7
|
import { fileURLToPath } from "node:url";
|
|
@@ -9,7 +10,13 @@ import { describe, it, expect } from "vitest";
|
|
|
9
10
|
import { create, type JsonObject } from "@bufbuild/protobuf";
|
|
10
11
|
import { ToolCallSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
|
|
11
12
|
import { ToolCallStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
12
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
ToolKind,
|
|
15
|
+
resolveToolKindByName,
|
|
16
|
+
normalizeToolResult,
|
|
17
|
+
extractShellIntent,
|
|
18
|
+
SHELL_INTENT_ARG_FIELD,
|
|
19
|
+
} from "../tool-view";
|
|
13
20
|
|
|
14
21
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
15
22
|
// sdk/typescript/src/execution/__tests__ -> repo root is five levels up.
|
|
@@ -115,6 +122,36 @@ describe("result-view fixtures", () => {
|
|
|
115
122
|
}
|
|
116
123
|
});
|
|
117
124
|
|
|
125
|
+
describe("intent-title fixtures", () => {
|
|
126
|
+
interface IntentCase {
|
|
127
|
+
name: string;
|
|
128
|
+
mcpServerSlug: string;
|
|
129
|
+
args: Record<string, unknown>;
|
|
130
|
+
intent: string | null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const fixture = loadFixture<{ argField: string; cases: IntentCase[] }>(
|
|
134
|
+
"intent-title.json",
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
it("loads the fixture and agrees on the wire key", () => {
|
|
138
|
+
expect(fixture.cases.length).toBeGreaterThan(0);
|
|
139
|
+
expect(SHELL_INTENT_ARG_FIELD).toBe(fixture.argField);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
for (const c of fixture.cases) {
|
|
143
|
+
it(`extracts ${c.name}${c.mcpServerSlug ? ` @${c.mcpServerSlug}` : ""} -> ${JSON.stringify(c.intent)}`, () => {
|
|
144
|
+
const toolCall = create(ToolCallSchema, {
|
|
145
|
+
id: c.name,
|
|
146
|
+
name: c.name,
|
|
147
|
+
mcpServerSlug: c.mcpServerSlug,
|
|
148
|
+
args: c.args as JsonObject,
|
|
149
|
+
});
|
|
150
|
+
expect(extractShellIntent(toolCall)).toBe(c.intent);
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
|
|
118
155
|
describe("resolveToolKind wire field precedence", () => {
|
|
119
156
|
it("prefers the wire tool_kind over the name fallback", () => {
|
|
120
157
|
const toolCall = create(ToolCallSchema, {
|
|
@@ -228,6 +228,58 @@ export function resolveToolKindByName(name: string, mcpServerSlug?: string): Too
|
|
|
228
228
|
return ToolKind.UNSPECIFIED;
|
|
229
229
|
}
|
|
230
230
|
|
|
231
|
+
// ---------------------------------------------------------------------------
|
|
232
|
+
// Intent: extractShellIntent
|
|
233
|
+
// ---------------------------------------------------------------------------
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* The tool-call argument that carries a model-authored intent phrase for
|
|
237
|
+
* SHELL tools (stigmer#276) — a short human description of what the command
|
|
238
|
+
* does and why, rendered as the row title with the command as secondary text.
|
|
239
|
+
*
|
|
240
|
+
* Two writers populate it, converged on one wire key by design: the native
|
|
241
|
+
* harness's tool-intent middleware (`backend/services/runner/src/middleware/
|
|
242
|
+
* tool-intent.ts`, `INTENT_ARG`) extends the shell tool's bind-time schema
|
|
243
|
+
* with it, and the Cursor harness's built-in Shell tool carries it natively.
|
|
244
|
+
* The shared, machine-checked contract is
|
|
245
|
+
* `test/fixtures/tool-view/intent-title.json` — keep the writers and this
|
|
246
|
+
* reader in lockstep.
|
|
247
|
+
*/
|
|
248
|
+
export const SHELL_INTENT_ARG_FIELD = "description";
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Args-level intent extraction: SHELL-kind-scoped on purpose, because
|
|
252
|
+
* `description` means other things on other tools (a task tool's description
|
|
253
|
+
* is the sub-agent subject, never a row title). Blank and non-string values
|
|
254
|
+
* degrade to null — callers fall back to their category label.
|
|
255
|
+
*
|
|
256
|
+
* The seam for surfaces that hold a pre-resolved kind and raw args instead of
|
|
257
|
+
* a full ToolCall: the CLI's headless snapshot projection and @stigmer/react's
|
|
258
|
+
* approval argsPreview path. Everything else should prefer
|
|
259
|
+
* {@link extractShellIntent}.
|
|
260
|
+
*/
|
|
261
|
+
export function shellIntentFromArgs(
|
|
262
|
+
kind: ToolKind,
|
|
263
|
+
args: Record<string, unknown> | undefined,
|
|
264
|
+
): string | null {
|
|
265
|
+
if (kind !== ToolKind.SHELL || !args) return null;
|
|
266
|
+
const value = args[SHELL_INTENT_ARG_FIELD];
|
|
267
|
+
if (typeof value !== "string") return null;
|
|
268
|
+
const trimmed = value.trim();
|
|
269
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Extracts the model-authored intent phrase from a SHELL tool call, or null
|
|
274
|
+
* when absent (legacy executions, models that skipped the optional arg, and
|
|
275
|
+
* every non-shell kind). See {@link SHELL_INTENT_ARG_FIELD}.
|
|
276
|
+
*/
|
|
277
|
+
export function extractShellIntent(
|
|
278
|
+
toolCall: Pick<ToolCall, "name" | "mcpServerSlug" | "toolKind" | "args">,
|
|
279
|
+
): string | null {
|
|
280
|
+
return shellIntentFromArgs(resolveToolKind(toolCall), toolCall.args as Args);
|
|
281
|
+
}
|
|
282
|
+
|
|
231
283
|
// ---------------------------------------------------------------------------
|
|
232
284
|
// Result: normalizeToolResult
|
|
233
285
|
// ---------------------------------------------------------------------------
|
package/src/gen/client.ts
CHANGED
|
@@ -136,4 +136,4 @@ export { type WorkflowExecutionInput } from "./workflowexecution.js";
|
|
|
136
136
|
export { WorkflowInstanceClient } from "./workflowinstance.js";
|
|
137
137
|
export { type WorkflowInstanceInput } from "./workflowinstance.js";
|
|
138
138
|
export { type ListParams, type ListResult, type DeleteResourceInput, type ResourceRef, type EnvSpecInput, type EnvVarInput, type Page } from "./types.js";
|
|
139
|
-
export { StigmerError, type ErrorCode, isNotFound, isUnauthenticated, isPermissionDenied, isRetryable } from "./errors.js";
|
|
139
|
+
export { StigmerError, type ErrorCode, isNotFound, isUnauthenticated, isPermissionDenied, isRetryable, isUnimplemented } from "./errors.js";
|
package/src/gen/errors.ts
CHANGED
|
@@ -74,3 +74,13 @@ export function isPermissionDenied(err: unknown): boolean {
|
|
|
74
74
|
export function isRetryable(err: unknown): boolean {
|
|
75
75
|
return err instanceof StigmerError && (err.code === "internal" || err.code === "unavailable");
|
|
76
76
|
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* The server does not implement the called RPC — the code clients key
|
|
80
|
+
* capability fallbacks on (e.g. the skill artifact transfer lane's unary
|
|
81
|
+
* fallback, stigmer#675/#701). Checks the raw connect code: Unimplemented
|
|
82
|
+
* deliberately has no ErrorCode mapping, so it surfaces as "unknown".
|
|
83
|
+
*/
|
|
84
|
+
export function isUnimplemented(err: unknown): boolean {
|
|
85
|
+
return err instanceof StigmerError && err.connectCode === Code.Unimplemented;
|
|
86
|
+
}
|
package/src/gen/skill.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { create } from "@bufbuild/protobuf";
|
|
|
7
7
|
import { createClient, type Client, type Transport } from "@connectrpc/connect";
|
|
8
8
|
import { SkillSchema, type Skill } from "@stigmer/protos/ai/stigmer/agentic/skill/v1/api_pb";
|
|
9
9
|
import { SkillCommandController } from "@stigmer/protos/ai/stigmer/agentic/skill/v1/command_pb";
|
|
10
|
-
import { SkillIdSchema, PushSkillRequestSchema, PushSkillFromExecutionArtifactRequestSchema, GetArtifactRequestSchema, GetArtifactResponseSchema, ListSkillVersionsInputSchema, ListSkillVersionsResponseSchema, type PushSkillRequest, type PushSkillFromExecutionArtifactRequest, type GetArtifactRequest, type GetArtifactResponse, type ListSkillVersionsInput, type ListSkillVersionsResponse } from "@stigmer/protos/ai/stigmer/agentic/skill/v1/io_pb";
|
|
10
|
+
import { SkillIdSchema, PushSkillRequestSchema, CreateSkillArtifactUploadUrlRequestSchema, SkillArtifactUploadUrlSchema, PushSkillFromExecutionArtifactRequestSchema, GetArtifactRequestSchema, GetArtifactResponseSchema, SkillArtifactDownloadUrlSchema, ListSkillVersionsInputSchema, ListSkillVersionsResponseSchema, type PushSkillRequest, type CreateSkillArtifactUploadUrlRequest, type SkillArtifactUploadUrl, type PushSkillFromExecutionArtifactRequest, type GetArtifactRequest, type GetArtifactResponse, type SkillArtifactDownloadUrl, type ListSkillVersionsInput, type ListSkillVersionsResponse } from "@stigmer/protos/ai/stigmer/agentic/skill/v1/io_pb";
|
|
11
11
|
import { SkillQueryController } from "@stigmer/protos/ai/stigmer/agentic/skill/v1/query_pb";
|
|
12
12
|
import { SkillSpecSchema } from "@stigmer/protos/ai/stigmer/agentic/skill/v1/spec_pb";
|
|
13
13
|
import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
|
|
@@ -36,6 +36,12 @@ export class SkillClient {
|
|
|
36
36
|
} catch (e) { throw wrapError(e); }
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
async createArtifactUploadUrl(input: CreateSkillArtifactUploadUrlRequest): Promise<SkillArtifactUploadUrl> {
|
|
40
|
+
try {
|
|
41
|
+
return await this.command.createArtifactUploadUrl(input);
|
|
42
|
+
} catch (e) { throw wrapError(e); }
|
|
43
|
+
}
|
|
44
|
+
|
|
39
45
|
async pushFromExecutionArtifact(input: PushSkillFromExecutionArtifactRequest): Promise<Skill> {
|
|
40
46
|
try {
|
|
41
47
|
return await this.command.pushFromExecutionArtifact(input);
|
|
@@ -72,6 +78,12 @@ export class SkillClient {
|
|
|
72
78
|
} catch (e) { throw wrapError(e); }
|
|
73
79
|
}
|
|
74
80
|
|
|
81
|
+
async getArtifactDownloadUrl(input: GetArtifactRequest): Promise<SkillArtifactDownloadUrl> {
|
|
82
|
+
try {
|
|
83
|
+
return await this.query.getArtifactDownloadUrl(input);
|
|
84
|
+
} catch (e) { throw wrapError(e); }
|
|
85
|
+
}
|
|
86
|
+
|
|
75
87
|
async listVersions(input: ListSkillVersionsInput): Promise<ListSkillVersionsResponse> {
|
|
76
88
|
try {
|
|
77
89
|
return await this.query.listVersions(input);
|
package/src/index.ts
CHANGED
|
@@ -41,6 +41,7 @@ export {
|
|
|
41
41
|
isUnauthenticated,
|
|
42
42
|
isPermissionDenied,
|
|
43
43
|
isRetryable,
|
|
44
|
+
isUnimplemented,
|
|
44
45
|
type ErrorCategory,
|
|
45
46
|
isConnectError,
|
|
46
47
|
classifyError,
|
|
@@ -104,6 +105,9 @@ export {
|
|
|
104
105
|
type SetCursorMemberKeyEnabledParams,
|
|
105
106
|
} from "./cursor-accounts.js";
|
|
106
107
|
|
|
108
|
+
// Provider standing client (platform operators only, read-only)
|
|
109
|
+
export { ProviderStandingClient } from "./provider-standing.js";
|
|
110
|
+
|
|
107
111
|
// Search client
|
|
108
112
|
export {
|
|
109
113
|
SearchClient,
|
|
@@ -271,6 +275,9 @@ export {
|
|
|
271
275
|
resolveToolKind,
|
|
272
276
|
resolveToolKindByName,
|
|
273
277
|
normalizeToolResult,
|
|
278
|
+
SHELL_INTENT_ARG_FIELD,
|
|
279
|
+
extractShellIntent,
|
|
280
|
+
shellIntentFromArgs,
|
|
274
281
|
type ToolResultView,
|
|
275
282
|
type ToolSearchMatch,
|
|
276
283
|
type ToolContentBlock,
|
|
@@ -287,6 +294,7 @@ export {
|
|
|
287
294
|
} from "./execution/file-review-fold.js";
|
|
288
295
|
export { toDisplayFileChange } from "./execution/to-display-file-change.js";
|
|
289
296
|
export { SkillClient, type SkillInput } from "./gen/skill.js";
|
|
297
|
+
export { RoutedSkillClient, MAX_INLINE_ARTIFACT_BYTES } from "./skill.js";
|
|
290
298
|
export {
|
|
291
299
|
WorkflowClient,
|
|
292
300
|
type WorkflowInput,
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { createClient, type Client, type Transport } from "@connectrpc/connect";
|
|
2
|
+
import { create } from "@bufbuild/protobuf";
|
|
3
|
+
import { ProviderStandingQueryController } from "@stigmer/protos/ai/stigmer/platform/providerstanding/v1/query_pb";
|
|
4
|
+
import {
|
|
5
|
+
GetProviderStandingViewInputSchema,
|
|
6
|
+
type ProviderStandingView,
|
|
7
|
+
} from "@stigmer/protos/ai/stigmer/platform/providerstanding/v1/io_pb";
|
|
8
|
+
import { wrapError } from "./gen/errors.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Client for platform provider standing (platform operators only).
|
|
12
|
+
*
|
|
13
|
+
* Serves the read-only operator view of the platform's LLM provider
|
|
14
|
+
* account health: the latest canary-probe verdict per provider (status,
|
|
15
|
+
* HTTP status, latency, bounded error summary, probe time) recorded by
|
|
16
|
+
* the hourly standing probe. Requires `can_view_provider_standing` on
|
|
17
|
+
* `platform:stigmer`. Cloud-only — the OSS Go server does not implement
|
|
18
|
+
* this controller.
|
|
19
|
+
*/
|
|
20
|
+
export class ProviderStandingClient {
|
|
21
|
+
private readonly query: Client<typeof ProviderStandingQueryController>;
|
|
22
|
+
|
|
23
|
+
constructor(transport: Transport) {
|
|
24
|
+
this.query = createClient(ProviderStandingQueryController, transport);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Retrieve the latest probe verdict for every platform provider. */
|
|
28
|
+
async getStandingView(): Promise<ProviderStandingView> {
|
|
29
|
+
try {
|
|
30
|
+
return await this.query.getProviderStandingView(
|
|
31
|
+
create(GetProviderStandingViewInputSchema, {}),
|
|
32
|
+
);
|
|
33
|
+
} catch (e) {
|
|
34
|
+
throw wrapError(e);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|