Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 21 additions & 10 deletions eslint.config.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
import eslint from "@eslint/js";
import tseslint from "typescript-eslint";

export default tseslint.config(
eslint.configs.recommended,
...tseslint.configs.recommended,
{
ignores: ["dist/", "node_modules/"],
}
);
import eslint from "@eslint/js";
import tseslint from "typescript-eslint";

export default tseslint.config(
eslint.configs.recommended,
...tseslint.configs.recommended,
{
rules: {
// Retired helpers keep their parameters so the public signature does not
// change, but never read them — the leading underscore is the standard
// way to say "deliberately unused".
"@typescript-eslint/no-unused-vars": [
"error",
{ argsIgnorePattern: "^_", varsIgnorePattern: "^_" },
],
},
},
{
ignores: ["dist/", "node_modules/"],
}
);
56 changes: 42 additions & 14 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import {
type OnrampResult,
APIError,
PaymentError,
RetiredEndpointError,
} from "./types";
// NOTE: @blockrun/clawrouter is loaded lazily inside smartChat() (see below),
// not imported at module top level. It is the model-routing engine and is only
Expand Down Expand Up @@ -1507,22 +1508,43 @@ export class LLMClient {
// ── PM convenience helpers (Predexon v2) ─────────────────────────────────
// Thin wrappers over pm() / pmQuery() for the most common v2 endpoints.

/** List canonical cross-venue markets (Predexon v2). Tier 1 ($0.001/call).
* Filter with venue, status, category, league, event_id, pagination_key. */
async pmMarkets(params?: Record<string, string>): Promise<Record<string, unknown>> {
return this.pm("markets", params);
/** RETIRED — `/v1/pm/markets` no longer exists.
*
* Predexon sunset market matching on 2026-07-20 and the whole canonical layer
* went with it, so this path returns 410 upstream. Use
* `pm("markets/search", { q })` for cross-venue lookups.
*
* Kept as a throwing stub rather than deleted so upgrading does not break
* property access; it throws before any network I/O.
*
* @throws {RetiredEndpointError} always. */
async pmMarkets(_params?: Record<string, string>): Promise<Record<string, unknown>> {
throw new RetiredEndpointError(
"/v1/pm/markets was sunset by Predexon on 2026-07-20 (upstream 410). " +
'Use pm("markets/search", { q }) for cross-venue lookups.',
);
}

/** List venue-native executable listings flattened across canonical markets
* (Predexon v2). Tier 1 ($0.001/call). */
async pmListings(params?: Record<string, string>): Promise<Record<string, unknown>> {
return this.pm("markets/listings", params);
/** RETIRED — `/v1/pm/markets/listings` no longer exists (410, sunset
* 2026-07-20 with market matching). Use `pm("markets/search", { q })`.
*
* @throws {RetiredEndpointError} always. */
async pmListings(_params?: Record<string, string>): Promise<Record<string, unknown>> {
throw new RetiredEndpointError(
"/v1/pm/markets/listings was sunset by Predexon on 2026-07-20 (upstream 410). " +
'Use pm("markets/search", { q }) for cross-venue lookups.',
);
}

/** Resolve a canonical Predexon outcome ID to its market context and venue
* listings. Tier 1 ($0.001/call). */
async pmOutcome(predexonId: string): Promise<Record<string, unknown>> {
return this.pm(`outcomes/${predexonId}`);
/** RETIRED — `/v1/pm/outcomes/{predexonId}` no longer exists (410, sunset
* 2026-07-20 with market matching). Use `pm("markets/search", { q })`.
*
* @throws {RetiredEndpointError} always. */
async pmOutcome(_predexonId: string): Promise<Record<string, unknown>> {
throw new RetiredEndpointError(
"/v1/pm/outcomes/{predexon_id} was sunset by Predexon on 2026-07-20 (upstream 410). " +
'Use pm("markets/search", { q }) for cross-venue lookups.',
);
}

/** Polymarket markets with cursor-based keyset pagination (use pagination_key).
Expand All @@ -1537,13 +1559,19 @@ export class LLMClient {
return this.pm("polymarket/events/keyset", params);
}

/** List available sports categories. Tier 1 ($0.001/call). */
/** List available sports categories. Tier 1 ($0.001/call).
*
* NOTE: upstream returns 500 for every `sports/*` path as of 2026-08-04.
* The route still resolves, so this works again the moment Predexon restores
* it, but do not build on it yet. */
async pmSportsCategories(): Promise<Record<string, unknown>> {
return this.pm("sports/categories");
}

/** List sports markets grouped by game. Filter with league, sport_type,
* status, venue. Tier 1 ($0.001/call). */
* status, venue. Tier 1 ($0.001/call).
*
* NOTE: upstream returns 500 for every `sports/*` path as of 2026-08-04. */
async pmSportsMarkets(params?: Record<string, string>): Promise<Record<string, unknown>> {
return this.pm("sports/markets", params);
}
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ export {
// Error classes
BlockrunError,
PaymentError,
RetiredEndpointError,
APIError,
} from "./types";
export {
Expand Down
14 changes: 14 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1109,6 +1109,20 @@ export class BlockrunError extends Error {
}
}

/**
* Thrown by a helper whose upstream endpoint no longer exists.
*
* Kept as a throwing method rather than deleted so upgrading does not break
* imports or property access — the failure is explicit and immediate instead of
* a round trip that returns 410/404.
*/
export class RetiredEndpointError extends BlockrunError {
constructor(message: string) {
super(message);
this.name = "RetiredEndpointError";
}
}

export class PaymentError extends BlockrunError {
constructor(message: string) {
super(message);
Expand Down
41 changes: 41 additions & 0 deletions test/unit/retired-endpoints.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Helpers for endpoints Predexon retired must fail fast, not silently 410.
//
// Probed upstream 2026-08-04 (3 runs each): /v1/pm/markets,
// /v1/pm/markets/listings and /v1/pm/outcomes/{id} all return
// 410 "This endpoint has been sunset as of 2026-07-20. Market matching is
// discontinued."
// The helpers are kept rather than deleted so upgrading does not break property
// access; this pins that they throw before any network I/O.
import { describe, it, expect } from "vitest";
import { LLMClient, RetiredEndpointError } from "../../src/index";

// No wallet, no network — if a helper reached fetch() these would hang or throw
// something other than RetiredEndpointError.
const client = Object.create(LLMClient.prototype) as LLMClient;

describe("retired Predexon helpers", () => {
it("pmMarkets throws with the sunset date", async () => {
await expect(client.pmMarkets()).rejects.toThrow(RetiredEndpointError);
await expect(client.pmMarkets()).rejects.toThrow(/2026-07-20/);
});

it("pmListings throws with the sunset date", async () => {
await expect(client.pmListings()).rejects.toThrow(RetiredEndpointError);
await expect(client.pmListings()).rejects.toThrow(/2026-07-20/);
});

it("pmOutcome throws with the sunset date", async () => {
await expect(client.pmOutcome("PXM-12345")).rejects.toThrow(RetiredEndpointError);
await expect(client.pmOutcome("PXM-12345")).rejects.toThrow(/2026-07-20/);
});

it("points at the surviving replacement", async () => {
await expect(client.pmMarkets()).rejects.toThrow(/markets\/search/);
});

it("is a BlockrunError, so existing catch blocks still catch it", async () => {
const err = await client.pmMarkets().catch((e) => e);
expect(err.name).toBe("RetiredEndpointError");
expect(err).toBeInstanceOf(Error);
});
});
Loading