pyyol 1.8.0 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.d.ts +24 -0
- package/dist/cli.js +101 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +11 -0
- package/dist/instrument.d.ts +35 -5
- package/dist/instrument.js +342 -47
- package/dist/movetools.d.ts +141 -0
- package/dist/movetools.js +486 -0
- package/dist/pricing.d.ts +26 -8
- package/dist/pricing.js +98 -14
- package/dist/providers.d.ts +27 -0
- package/dist/providers.js +140 -0
- package/dist/scaffold.d.ts +74 -0
- package/dist/scaffold.js +276 -0
- package/dist/telemetry.d.ts +24 -0
- package/dist/telemetry.js +62 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +6 -2
- package/rules/llms-full.txt +736 -27
- package/skill/SKILL.md +1 -0
- package/skill/references/telemetry.md +55 -0
package/dist/cli.d.ts
CHANGED
|
@@ -15,5 +15,29 @@ export declare function connectionToken(a: Args, c: creds.Credentials | null): {
|
|
|
15
15
|
token: string;
|
|
16
16
|
usingAgentKey: boolean;
|
|
17
17
|
};
|
|
18
|
+
/** True if the agent's entry file appears to send a system prompt, false if not, null if unknown.
|
|
19
|
+
*
|
|
20
|
+
* A shallow source scan, deliberately: it recognises Anthropic's `system=` and OpenAI's system /
|
|
21
|
+
* developer roles. Being approximate is fine because a wrong answer here is a HINT, not a
|
|
22
|
+
* decision — the authoritative value is the `scaffold` field on a real decision, which is what the
|
|
23
|
+
* unknown case points at.
|
|
24
|
+
*
|
|
25
|
+
* "developer" is included because it is OpenAI's newer name for the system role, and missing it
|
|
26
|
+
* would tell a correctly built agent it is ineligible — worse than saying nothing. */
|
|
27
|
+
export declare function scaffoldHint(entry: string | undefined): Promise<boolean | null>;
|
|
28
|
+
/** Report whether this agent will actually earn Verified, and if not, exactly why.
|
|
29
|
+
*
|
|
30
|
+
* Three things decide it and each fails silently on its own: routing (without it the platform sees
|
|
31
|
+
* no calls at all), a system prompt (without one the harness cannot be fingerprinted, so the agent
|
|
32
|
+
* is excluded from paired model comparison), and coverage (the share of decisions actually proven,
|
|
33
|
+
* against the threshold the verified tier requires).
|
|
34
|
+
*
|
|
35
|
+
* An agent can run perfectly and earn nothing. Nothing else in the toolchain says so, and learning
|
|
36
|
+
* it from an empty leaderboard row weeks later is the failure this prevents. */
|
|
37
|
+
export declare function printVerifiedReadiness(base: string, c: {
|
|
38
|
+
accessToken?: string;
|
|
39
|
+
} | null, cfg: {
|
|
40
|
+
entry?: string;
|
|
41
|
+
} | null): Promise<void>;
|
|
18
42
|
export declare function apiRequest(method: string, url: string, token: string, body: unknown): Promise<[number, any]>;
|
|
19
43
|
export declare function main(argv?: string[]): Promise<number>;
|
package/dist/cli.js
CHANGED
|
@@ -636,7 +636,31 @@ async function cmdWallet(a) {
|
|
|
636
636
|
console.error(`${BAD} not logged in — run \`pyyol login\` first.`);
|
|
637
637
|
return 2;
|
|
638
638
|
}
|
|
639
|
+
// AN AGENT KEY CANNOT READ THE OWNER'S TREASURY, and must not be sent here.
|
|
640
|
+
//
|
|
641
|
+
// The fallback chain above ends at PYYOL_TOKEN, which the deployment docs define as
|
|
642
|
+
// the sk_arena_… AGENT key for CI and containers. That key resolves server-side to
|
|
643
|
+
// its owner's user id, so this command used to work with it — an agent credential
|
|
644
|
+
// reading its owner's full balance and ledger. The arena now requires user scope on
|
|
645
|
+
// /v1/user/wallet and answers 403, which would surface here as an opaque
|
|
646
|
+
// "could not fetch wallet (403)".
|
|
647
|
+
//
|
|
648
|
+
// Refusing locally, by shape, is better than relaying that: it names the credential
|
|
649
|
+
// actually in play (easy to miss when it arrives from the environment rather than a
|
|
650
|
+
// flag) and says which one the command needs.
|
|
651
|
+
if (token.startsWith(AGENT_KEY_PREFIX)) {
|
|
652
|
+
console.error(`${BAD} \`pyyol wallet\` shows the OWNER's treasury, so it needs your dashboard login — ` +
|
|
653
|
+
`not an agent key.\n` +
|
|
654
|
+
` The token in use is an agent key (sk_arena_…), probably from $PYYOL_TOKEN.\n` +
|
|
655
|
+
` Run \`pyyol login\` on this machine, or unset PYYOL_TOKEN for this command.`);
|
|
656
|
+
return 2;
|
|
657
|
+
}
|
|
639
658
|
const [st, w] = await apiGet(`${base}/v1/user/wallet`, token);
|
|
659
|
+
if (st === 403) {
|
|
660
|
+
console.error(`${BAD} this credential is not allowed to read the owner's treasury. ` +
|
|
661
|
+
`Sign in with \`pyyol login\` and try again.`);
|
|
662
|
+
return 1;
|
|
663
|
+
}
|
|
640
664
|
if (st !== 200) {
|
|
641
665
|
console.error(`${BAD} could not fetch wallet (${st}): ${JSON.stringify(w)}`);
|
|
642
666
|
return 1;
|
|
@@ -894,9 +918,86 @@ async function cmdDoctor(a) {
|
|
|
894
918
|
allOk = allOk && ok;
|
|
895
919
|
console.log(` ${ok ? OK : BAD} ${name.padEnd(20)} ${detail}`);
|
|
896
920
|
}
|
|
921
|
+
// Verified-tier readiness. Separate from the checks above because none of these is a failure of
|
|
922
|
+
// the agent: it will run, it just will not be RANKED. Conflating the two trains people to ignore
|
|
923
|
+
// a red mark that sometimes means nothing.
|
|
924
|
+
//
|
|
925
|
+
// Mirrors the Python SDK's section deliberately, down to the wording, because a developer must
|
|
926
|
+
// not get a different answer about their own eligibility depending on which SDK they installed.
|
|
927
|
+
await printVerifiedReadiness(base, c, cfg);
|
|
897
928
|
console.log("\n" + (allOk ? "✓ ready — `pyyol dev` to practice, `pyyol play <arena>` to compete." : "fix the ✗ items above."));
|
|
898
929
|
return allOk ? 0 : 1;
|
|
899
930
|
}
|
|
931
|
+
/** True if the agent's entry file appears to send a system prompt, false if not, null if unknown.
|
|
932
|
+
*
|
|
933
|
+
* A shallow source scan, deliberately: it recognises Anthropic's `system=` and OpenAI's system /
|
|
934
|
+
* developer roles. Being approximate is fine because a wrong answer here is a HINT, not a
|
|
935
|
+
* decision — the authoritative value is the `scaffold` field on a real decision, which is what the
|
|
936
|
+
* unknown case points at.
|
|
937
|
+
*
|
|
938
|
+
* "developer" is included because it is OpenAI's newer name for the system role, and missing it
|
|
939
|
+
* would tell a correctly built agent it is ineligible — worse than saying nothing. */
|
|
940
|
+
export async function scaffoldHint(entry) {
|
|
941
|
+
if (!entry)
|
|
942
|
+
return null;
|
|
943
|
+
const path = String(entry).split(":")[0];
|
|
944
|
+
try {
|
|
945
|
+
const { readFileSync } = await import("node:fs");
|
|
946
|
+
const src = readFileSync(path, "utf8");
|
|
947
|
+
return ["system:", "system =", '"system"', "'system'", '"developer"', "'developer'"].some((n) => src.includes(n));
|
|
948
|
+
}
|
|
949
|
+
catch {
|
|
950
|
+
return null;
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
/** Report whether this agent will actually earn Verified, and if not, exactly why.
|
|
954
|
+
*
|
|
955
|
+
* Three things decide it and each fails silently on its own: routing (without it the platform sees
|
|
956
|
+
* no calls at all), a system prompt (without one the harness cannot be fingerprinted, so the agent
|
|
957
|
+
* is excluded from paired model comparison), and coverage (the share of decisions actually proven,
|
|
958
|
+
* against the threshold the verified tier requires).
|
|
959
|
+
*
|
|
960
|
+
* An agent can run perfectly and earn nothing. Nothing else in the toolchain says so, and learning
|
|
961
|
+
* it from an empty leaderboard row weeks later is the failure this prevents. */
|
|
962
|
+
export async function printVerifiedReadiness(base, c, cfg) {
|
|
963
|
+
const { gatewayBaseUrl } = await import("./instrument.js");
|
|
964
|
+
const { explain, ISSUE_NO_SYSTEM_PROMPT } = await import("./scaffold.js");
|
|
965
|
+
console.log("\nverified tier");
|
|
966
|
+
const routed = Boolean(gatewayBaseUrl("anthropic") || gatewayBaseUrl("openai"));
|
|
967
|
+
console.log(` ${routed ? OK : WARN} ${"gateway routing".padEnd(20)} ` +
|
|
968
|
+
(routed
|
|
969
|
+
? "on — model calls are server-observed"
|
|
970
|
+
: "off — call pyyol.route(client) after pyyol.instrument(); without it no decision can be " +
|
|
971
|
+
"proven and this agent cannot appear on the model board"));
|
|
972
|
+
const hint = await scaffoldHint(cfg?.entry);
|
|
973
|
+
if (hint === null) {
|
|
974
|
+
console.log(` ${WARN} ${"system prompt".padEnd(20)} could not inspect the agent source; run \`pyyol dev\` ` +
|
|
975
|
+
"and check `scaffold` on a decision in the trace");
|
|
976
|
+
}
|
|
977
|
+
else if (hint) {
|
|
978
|
+
console.log(` ${OK} ${"system prompt".padEnd(20)} found — the harness can be fingerprinted, so this ` +
|
|
979
|
+
"agent is eligible for paired model comparison");
|
|
980
|
+
}
|
|
981
|
+
else {
|
|
982
|
+
// The shared explanation, never a paraphrase: the SDKs, the trace and this command have to give
|
|
983
|
+
// a developer the same sentence about one rule.
|
|
984
|
+
console.log(` ${WARN} ${"system prompt".padEnd(20)} none found. ${explain(ISSUE_NO_SYSTEM_PROMPT)}`);
|
|
985
|
+
}
|
|
986
|
+
if (base && c?.accessToken) {
|
|
987
|
+
const [st, body] = await apiGet(`${base}/v1/gw/coverage`, c.accessToken);
|
|
988
|
+
const b = body;
|
|
989
|
+
if (st === 200 && b?.decisions) {
|
|
990
|
+
const cov = Number(b.coverage ?? 0);
|
|
991
|
+
const mark = cov >= 0.9 ? OK : WARN;
|
|
992
|
+
console.log(` ${mark} ${"coverage".padEnd(20)} ${b.bound_decisions ?? 0}/${b.decisions} decisions ` +
|
|
993
|
+
`proven (${(cov * 100).toFixed(1)}%)` +
|
|
994
|
+
(cov >= 0.9 ? "" : " — below the 90% the verified tier requires"));
|
|
995
|
+
}
|
|
996
|
+
else if (st === 200) {
|
|
997
|
+
console.log(` ${WARN} ${"coverage".padEnd(20)} no decisions recorded yet — play a match first`);
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
900
1001
|
async function cmdUpdate() {
|
|
901
1002
|
console.log(`pyyol ${SDK_VERSION}`);
|
|
902
1003
|
try {
|
package/dist/index.d.ts
CHANGED
|
@@ -27,4 +27,6 @@ export { instrument, uninstrument, recordResponse, extractUsage, patchPrototype
|
|
|
27
27
|
export { route, enableGateway, disableGateway, gatewayBaseUrl, gatewayHeaders } from "./instrument.js";
|
|
28
28
|
export type { ExtractedUsage } from "./instrument.js";
|
|
29
29
|
export { estimateCost, rateFor, isKnown, canonical, PRICING_VERSION } from "./pricing.js";
|
|
30
|
+
export { moveTool, moveToolChoice, moveToolName, moveFromResponse, boundMove, boundPlan, canonPlan, PLAN_KEY, MAX_SPAN_ROUNDS, canonMove, canonGoofspiel, canonMafia, canonMonopoly, NO_TARGET, TOOL_GOOFSPIEL, TOOL_MAFIA, TOOL_MONOPOLY, GAME_GOOFSPIEL, GAME_MAFIA, GAME_MONOPOLY, } from "./movetools.js";
|
|
30
31
|
export type { Rate, CostArgs } from "./pricing.js";
|
|
32
|
+
export { fingerprint as scaffoldFingerprint, fromRequest as scaffoldFromRequest, eligibleForPairing as scaffoldEligibleForPairing, SCAFFOLD_VERSION, } from "./scaffold.js";
|
package/dist/index.js
CHANGED
|
@@ -21,3 +21,14 @@ export { Tracer, Span, currentSpan, matchTraceId, currentUsage, UsageAccumulator
|
|
|
21
21
|
export { instrument, uninstrument, recordResponse, extractUsage, patchPrototype } from "./instrument.js";
|
|
22
22
|
export { route, enableGateway, disableGateway, gatewayBaseUrl, gatewayHeaders } from "./instrument.js";
|
|
23
23
|
export { estimateCost, rateFor, isKnown, canonical, PRICING_VERSION } from "./pricing.js";
|
|
24
|
+
// Structured move tools: how an agent proves its MODEL chose the move it played. Routing
|
|
25
|
+
// through the gateway proves a call happened for a turn; a tool call is what proves the
|
|
26
|
+
// model's answer became the move. See src/movetools.ts.
|
|
27
|
+
export { moveTool, moveToolChoice, moveToolName, moveFromResponse, boundMove,
|
|
28
|
+
// Range bindings: one completion that decided several rounds. Coverage counts DECISIONS a
|
|
29
|
+
// model made, not calls, so batching no longer costs an agent its verified share.
|
|
30
|
+
boundPlan, canonPlan, PLAN_KEY, MAX_SPAN_ROUNDS, canonMove, canonGoofspiel, canonMafia, canonMonopoly, NO_TARGET, TOOL_GOOFSPIEL, TOOL_MAFIA, TOOL_MONOPOLY, GAME_GOOFSPIEL, GAME_MAFIA, GAME_MONOPOLY, } from "./movetools.js";
|
|
31
|
+
// Scaffold fingerprinting: the harness identity that makes a paired model comparison
|
|
32
|
+
// possible (same scaffold, different model). Exported so a developer can print their own
|
|
33
|
+
// fingerprint and confirm it is stable before relying on it.
|
|
34
|
+
export { fingerprint as scaffoldFingerprint, fromRequest as scaffoldFromRequest, eligibleForPairing as scaffoldEligibleForPairing, SCAFFOLD_VERSION, } from "./scaffold.js";
|
package/dist/instrument.d.ts
CHANGED
|
@@ -2,10 +2,26 @@ type Any = any;
|
|
|
2
2
|
/** Enable gateway routing (called by the runtime in ranked mode; safe in tests). */
|
|
3
3
|
export declare function enableGateway(agentKey: string, baseUrl: string): void;
|
|
4
4
|
export declare function disableGateway(): void;
|
|
5
|
-
/**
|
|
5
|
+
/**
|
|
6
|
+
* The baseURL a provider client should point at, or "" when routing is off or the provider
|
|
7
|
+
* cannot be routed.
|
|
8
|
+
*
|
|
9
|
+
* Returns "" for a LOCAL/self-hosted provider, deliberately: the gateway runs on Pyyol's side
|
|
10
|
+
* and cannot reach a model server on the developer's own machine, so pointing a client at it
|
|
11
|
+
* would break every call. That play is unverified — and also free, so no cost attribution is
|
|
12
|
+
* lost either.
|
|
13
|
+
*/
|
|
6
14
|
export declare function gatewayBaseUrl(provider: string): string;
|
|
7
15
|
/** The X-Pyyol-* identity headers for the current turn (empty if routing off). */
|
|
8
16
|
export declare function gatewayHeaders(): Record<string, string>;
|
|
17
|
+
/** The provider for one instrumented call.
|
|
18
|
+
*
|
|
19
|
+
* `patchedAs` is the SDK we wrapped (which wire format this is). The client's baseURL
|
|
20
|
+
* is consulted first and wins. A baseURL pointing at the PYYOL GATEWAY is not used for
|
|
21
|
+
* attribution — it says the call was proxied, not who served it — so the upstream is
|
|
22
|
+
* recovered by PARSING the gateway path (/gw/<slug>[/v1]) rather than matching a table, so a
|
|
23
|
+
* provider added tomorrow is attributed correctly without touching this. */
|
|
24
|
+
export declare function resolveCallProvider(resource: Any, patchedAs: string): string;
|
|
9
25
|
/** Point a provider client at the Pyyol Gateway (sets its baseURL). Explicit, robust
|
|
10
26
|
* opt-in that operates on the given instance. Returns the client. No-op when routing
|
|
11
27
|
* is off or the provider can't be determined. */
|
|
@@ -15,12 +31,26 @@ export interface ExtractedUsage {
|
|
|
15
31
|
provider: string;
|
|
16
32
|
promptTokens: number;
|
|
17
33
|
completionTokens: number;
|
|
34
|
+
/** Prompt-cache READ tokens. */
|
|
18
35
|
cachedTokens: number;
|
|
36
|
+
/** Prompt-cache WRITE/creation tokens. Billed at 1.25x input on Anthropic, so an
|
|
37
|
+
* agent's most expensive tokens were previously recorded as zero. */
|
|
38
|
+
cachedWriteTokens: number;
|
|
19
39
|
reasoningTokens: number;
|
|
20
40
|
}
|
|
21
|
-
/**
|
|
22
|
-
*
|
|
23
|
-
*
|
|
41
|
+
/**
|
|
42
|
+
* Pull normalized usage from ANY provider response, or null if it carries none.
|
|
43
|
+
*
|
|
44
|
+
* Normalizes onto ONE convention: promptTokens is the total billable input, with cache reads and
|
|
45
|
+
* writes as SUBSETS of it. Providers genuinely disagree and the disagreement is silent, so the rule
|
|
46
|
+
* follows the WORD USED rather than a vendor list:
|
|
47
|
+
*
|
|
48
|
+
* - a PROMPT-family key names the whole prompt, so cache is already inside it
|
|
49
|
+
* - an INPUT-family key names fresh input, so cache is billed on top
|
|
50
|
+
*
|
|
51
|
+
* A reported total cross-checks the additive case, so a provider using "input" for a
|
|
52
|
+
* cache-inclusive total is corrected by its own arithmetic instead of being over-counted.
|
|
53
|
+
*/
|
|
24
54
|
export declare function extractUsage(resp: Any): ExtractedUsage | null;
|
|
25
55
|
/** Record usage from a provider response: compute cost, add to the turn
|
|
26
56
|
* accumulator, and emit a Lens model_call span. Returns the extracted usage (or
|
|
@@ -31,7 +61,7 @@ export declare function recordResponse(resp: Any, o?: {
|
|
|
31
61
|
}): ExtractedUsage | null;
|
|
32
62
|
/** @internal Wrap `proto[method]` so its resolved return value is recorded.
|
|
33
63
|
* Idempotent and fully guarded. Exported for tests. */
|
|
34
|
-
export declare function patchPrototype(proto: Any, method: string, provider: string): boolean;
|
|
64
|
+
export declare function patchPrototype(proto: Any, method: string, provider: string, endpoint?: string): boolean;
|
|
35
65
|
/** Auto-capture LLM usage from installed providers. Pass e.g. `["openai"]` to limit
|
|
36
66
|
* which are patched; default patches all supported providers that are installed.
|
|
37
67
|
* Returns the list actually instrumented. Safe to call more than once. */
|