diff --git a/.github/workflows/contract-tests.yml b/.github/workflows/contract-tests.yml new file mode 100644 index 0000000..2bb937f --- /dev/null +++ b/.github/workflows/contract-tests.yml @@ -0,0 +1,54 @@ +name: Contract tests + +# Issue #37 phase 3 (#47): enforce, on every PR, that the CLI's API calls and +# the Python SDK's consumed models stay aligned with the website-mirrored +# public OpenAPI contract. The cli/python *-publish workflows only run on +# release tags, so without this the contract guards would not gate PRs. + +on: + pull_request: + paths: + - "docs/openapi/**" + - "packages/cli/src/**" + - "packages/cli/test/openapi-contract.test.mjs" + - "packages/python-sdk/qveris/**" + - "packages/python-sdk/tests/test_openapi_contract.py" + - ".github/workflows/contract-tests.yml" + push: + branches: + - main + paths: + - "docs/openapi/**" + - "packages/cli/src/**" + - "packages/cli/test/openapi-contract.test.mjs" + - "packages/python-sdk/qveris/**" + - "packages/python-sdk/tests/test_openapi_contract.py" + - ".github/workflows/contract-tests.yml" + +permissions: + contents: read + +jobs: + cli-contract: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + - name: CLI ↔ OpenAPI contract test + working-directory: packages/cli + run: node --test test/openapi-contract.test.mjs + + python-contract: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.11" + - name: Python SDK ↔ generated-contract test + working-directory: packages/python-sdk + run: | + python -m pip install --quiet pytest "pydantic>=2.0.0" + PYTHONPATH=. python -m pytest tests/test_openapi_contract.py -q diff --git a/.github/workflows/openapi-types.yml b/.github/workflows/openapi-types.yml new file mode 100644 index 0000000..33a9e2a --- /dev/null +++ b/.github/workflows/openapi-types.yml @@ -0,0 +1,69 @@ +name: OpenAPI generated types + +# Issue #37 phase 2: the checked-in generated artifacts must be reproducible +# from docs/openapi/qveris-public-api.openapi.json. Regenerate with the pinned +# generators and fail if the working tree differs (contract / generator drift). + +on: + pull_request: + paths: + - "docs/openapi/**" + - "packages/mcp/src/generated/**" + - "packages/python-sdk/qveris/generated/**" + - "packages/mcp/package.json" + - "packages/python-sdk/pyproject.toml" + - ".github/workflows/openapi-types.yml" + push: + branches: + - main + paths: + - "docs/openapi/**" + - "packages/mcp/src/generated/**" + - "packages/python-sdk/qveris/generated/**" + - "packages/mcp/package.json" + - "packages/python-sdk/pyproject.toml" + - ".github/workflows/openapi-types.yml" + +permissions: + contents: read + +jobs: + regen-and-diff: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Regenerate MCP TypeScript types + run: npx --yes openapi-typescript@7.4.4 docs/openapi/qveris-public-api.openapi.json -o packages/mcp/src/generated/openapi.d.ts + + - name: Regenerate Python SDK models + run: | + python -m pip install --quiet "datamodel-code-generator==0.26.3" + python -m datamodel_code_generator \ + --input docs/openapi/qveris-public-api.openapi.json \ + --input-file-type openapi \ + --output packages/python-sdk/qveris/generated/openapi_models.py \ + --output-model-type pydantic_v2.BaseModel \ + --target-python-version 3.8 \ + --use-schema-description \ + --disable-timestamp + + - name: Fail on drift + run: | + if ! git diff --exit-code -- \ + packages/mcp/src/generated/openapi.d.ts \ + packages/python-sdk/qveris/generated/openapi_models.py; then + echo "::error::Generated OpenAPI artifacts are out of date. Run 'npm --prefix packages/mcp run gen:openapi' and regenerate the Python models, then commit." + exit 1 + fi diff --git a/packages/cli/test/openapi-contract.test.mjs b/packages/cli/test/openapi-contract.test.mjs new file mode 100644 index 0000000..55d98fd --- /dev/null +++ b/packages/cli/test/openapi-contract.test.mjs @@ -0,0 +1,46 @@ +// Issue #37 phase 3 (#47): CLI is JavaScript/MJS, so before any type +// generation it gets contract tests that assert every endpoint + method the +// CLI actually calls exists in the website-mirrored public OpenAPI spec. +// This catches CLI/contract drift without introducing a generator. + +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; +import { fileURLToPath } from "node:url"; +import path from "node:path"; +import test from "node:test"; + +const here = path.dirname(fileURLToPath(import.meta.url)); +const specPath = path.resolve( + here, + "../../../docs/openapi/qveris-public-api.openapi.json", +); + +const spec = JSON.parse(readFileSync(specPath, "utf8")); + +// Endpoints the CLI calls today, kept in sync with packages/cli/src/client/api.mjs. +// (path, method) — method lowercased to match OpenAPI operation keys. +const CLI_OPERATIONS = [ + ["/search", "post"], + ["/tools/by-ids", "post"], + ["/tools/execute", "post"], + ["/auth/credits", "get"], + ["/auth/usage/history/v2", "get"], + ["/auth/credits/ledger", "get"], +]; + +test("OpenAPI spec is structurally usable", () => { + assert.equal(typeof spec, "object"); + assert.ok(spec.info && typeof spec.info.version === "string", "info.version present"); + assert.ok(spec.paths && typeof spec.paths === "object", "paths present"); +}); + +for (const [p, method] of CLI_OPERATIONS) { + test(`contract covers CLI call ${method.toUpperCase()} ${p}`, () => { + const item = spec.paths[p]; + assert.ok(item, `path ${p} missing from public OpenAPI contract`); + assert.ok( + item[method], + `method ${method.toUpperCase()} for ${p} missing — CLI calls it but the contract does not declare it`, + ); + }); +} diff --git a/packages/mcp/package.json b/packages/mcp/package.json index 00cb5c0..8591d15 100644 --- a/packages/mcp/package.json +++ b/packages/mcp/package.json @@ -1,7 +1,7 @@ { "name": "@qverisai/mcp", - "version": "0.6.0", - "description": "QVeris MCP server for agent tool discovery, inspection, and calling", + "version": "0.6.0", + "description": "QVeris MCP server for agent tool discovery, inspection, and calling", "keywords": [ "qveris", "mcp", @@ -14,12 +14,12 @@ "license": "MIT", "repository": { "type": "git", - "url": "https://github.com/QVerisAI/qveris-agent-toolkit", + "url": "https://github.com/QVerisAI/qveris-agent-toolkit", "directory": "packages/mcp" }, "homepage": "https://qveris.ai", "bugs": { - "url": "https://github.com/QVerisAI/qveris-agent-toolkit/issues" + "url": "https://github.com/QVerisAI/qveris-agent-toolkit/issues" }, "type": "module", "main": "dist/index.js", @@ -38,7 +38,8 @@ "test:watch": "vitest", "test:coverage": "vitest run --coverage", "prepublishOnly": "npm run build", - "typecheck": "tsc --noEmit" + "typecheck": "tsc --noEmit", + "gen:openapi": "openapi-typescript ../../docs/openapi/qveris-public-api.openapi.json -o src/generated/openapi.d.ts && npx prettier --write src/generated/openapi.d.ts" }, "dependencies": { "@modelcontextprotocol/sdk": "^1.0.0", @@ -47,6 +48,7 @@ "devDependencies": { "@types/node": "^22.10.1", "@types/uuid": "^10.0.0", + "openapi-typescript": "7.4.4", "typescript": "^5.7.2", "vitest": "^2.1.0" }, diff --git a/packages/mcp/src/generated/openapi.d.ts b/packages/mcp/src/generated/openapi.d.ts new file mode 100644 index 0000000..1d0bad3 --- /dev/null +++ b/packages/mcp/src/generated/openapi.d.ts @@ -0,0 +1,2110 @@ +/** + * This file was auto-generated by openapi-typescript. + * Do not make direct changes to the file. + */ + +export interface paths { + "/auth/account/settlement-history": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get Account Settlement History + * @description 获取账号中心结算历史轻量 feed。 + * + * This first implementation serves the immutable ledger summary path used + * after readiness confirms the ledger is complete for the requested range. + */ + get: operations["get_account_settlement_history_api_v1_auth_account_settlement_history_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/auth/credits": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get Current User Credits + * @description 查询当前用户的积分信息,包括各类积分明细和剩余总积分。支持 API Key 和 JWT 认证。 + */ + get: operations["get_current_user_credits_api_v1_auth_credits_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/auth/credits/ledger": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Query credits ledger + * @description Query immutable final credit balance movements. Usage audit explains request outcomes; the credits ledger explains signed balance changes. Negative `amount_credits` values consume credits and positive values grant credits. + */ + get: operations["get_credits_ledger_api_v1_auth_credits_ledger_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/auth/credits/ledger-readiness": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get Credits Ledger Readiness + * @description Check whether this user's account history can safely switch to credits_ledger. + */ + get: operations["get_credits_ledger_readiness_api_v1_auth_credits_ledger_readiness_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/auth/credits/ledger/export": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Export Credits Ledger + * @description 导出用户 credits ledger 账本,供账号中心和运营审计使用。 + */ + get: operations["export_credits_ledger_api_v1_auth_credits_ledger_export_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/auth/credits/ledger/{entry_id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get Credits Ledger Entry + * @description 获取单条 credits ledger detail,用于账单详情展开和审计追溯。 + */ + get: operations["get_credits_ledger_entry_api_v1_auth_credits_ledger__entry_id__get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/auth/usage/credits-spent": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get Usage Credits Spent + * @description Return the total credits spent by the current user in the given date range. + * + * Uses a single server-side SUM query — no pagination limit — so results are + * accurate even for high-volume users with thousands of events in the window. + */ + get: operations["get_usage_credits_spent_api_v1_auth_usage_credits_spent_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/auth/usage/history/v2": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Query usage audit history + * @description Query canonical usage events to confirm request success, failure, and final charge outcome. Agent, CLI, and MCP clients should prefer precise filters such as `execution_id` or `summary=true` with a bounded `limit` instead of dumping full history. + */ + get: operations["get_usage_history_v2_api_v1_auth_usage_history_v2_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/auth/usage/history/v2/export": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Export Usage History V2 + * @description 导出 request-level usage audit,供用户与支持审计使用。 + */ + get: operations["export_usage_history_v2_api_v1_auth_usage_history_v2_export_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/auth/usage/history/v2/summary": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get Usage History V2 Summary + * @description 获取轻量 usage events summary,用于账号页列表快速渲染。 + */ + get: operations["get_usage_history_v2_summary_api_v1_auth_usage_history_v2_summary_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/auth/usage/history/v2/{event_id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get Usage History V2 Entry + * @description 获取单条 usage event 详情。 + */ + get: operations["get_usage_history_v2_entry_api_v1_auth_usage_history_v2__event_id__get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/auth/verify-token": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Verify Token + * @description 验证JWT token是否有效和是否过期 + * + * Args: + * token: JWT访问令牌 + * + * Returns: + * TokenVerificationResponse: 包含token验证结果的响应 + */ + post: operations["verify_token_api_v1_auth_verify_token_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/credits": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get Current User Credits + * @description 查询当前用户的积分信息,包括各类积分明细和剩余总积分。支持 API Key 和 JWT 认证。 + */ + get: operations["get_current_user_credits_api_v1_credits_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/providers": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get Providers + * @description Get providers from the real API with optional filtering and pagination + */ + get: operations["get_providers_api_v1_providers_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/providers/categories": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get Provider Categories + * @description Get provider categories from the real API + */ + get: operations["get_provider_categories_api_v1_providers_categories_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/search": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Discover capabilities + * @description Find ranked capabilities from a natural-language query. Discover is free. Results may include `expected_cost` and `billing_rule` so clients can estimate a later Call. + */ + post: operations["search_api_v1_search_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/tools/by-ids": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Inspect capabilities by id + * @description Fetch full capability metadata for one or more tool ids. Inspect is free and returns the same capability shape as Discover, including parameter schema, examples, quality signals, and cost estimates. + */ + post: operations["get_tools_by_ids_api_v1_tools_by_ids_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/tools/execute": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Call a capability + * @description Execute a selected capability. Call may consume credits according to the capability `billing_rule`; the response can include a pre-settlement `billing` statement, while final settlement is available through usage audit and credits ledger endpoints. + */ + post: operations["execute_api_v1_tools_execute_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; +} +export type webhooks = Record; +export interface components { + schemas: { + /** APIResponse[CreditsLedgerItem] */ + APIResponse_CreditsLedgerItem_: { + /** Status */ + status: string; + /** Message */ + message: string; + /** + * Status Code + * @default 0 + */ + status_code: number; + data?: components["schemas"]["CreditsLedgerItem"] | null; + /** Message Key */ + message_key?: string | null; + }; + /** APIResponse[CreditsLedgerReadinessResponse] */ + APIResponse_CreditsLedgerReadinessResponse_: { + /** Status */ + status: string; + /** Message */ + message: string; + /** + * Status Code + * @default 0 + */ + status_code: number; + data?: components["schemas"]["CreditsLedgerReadinessResponse"] | null; + /** Message Key */ + message_key?: string | null; + }; + /** APIResponse[CreditsLedgerResponse] */ + APIResponse_CreditsLedgerResponse_: { + /** Status */ + status: string; + /** Message */ + message: string; + /** + * Status Code + * @default 0 + */ + status_code: number; + data?: components["schemas"]["CreditsLedgerResponse"] | null; + /** Message Key */ + message_key?: string | null; + }; + /** APIResponse[SettlementHistoryResponse] */ + APIResponse_SettlementHistoryResponse_: { + /** Status */ + status: string; + /** Message */ + message: string; + /** + * Status Code + * @default 0 + */ + status_code: number; + data?: components["schemas"]["SettlementHistoryResponse"] | null; + /** Message Key */ + message_key?: string | null; + }; + /** APIResponse[TokenVerificationResponse] */ + APIResponse_TokenVerificationResponse_: { + /** Status */ + status: string; + /** Message */ + message: string; + /** + * Status Code + * @default 0 + */ + status_code: number; + data?: components["schemas"]["TokenVerificationResponse"] | null; + /** Message Key */ + message_key?: string | null; + }; + /** APIResponse[UsageCreditsSpentResponse] */ + APIResponse_UsageCreditsSpentResponse_: { + /** Status */ + status: string; + /** Message */ + message: string; + /** + * Status Code + * @default 0 + */ + status_code: number; + data?: components["schemas"]["UsageCreditsSpentResponse"] | null; + /** Message Key */ + message_key?: string | null; + }; + /** APIResponse[UsageEventItem] */ + APIResponse_UsageEventItem_: { + /** Status */ + status: string; + /** Message */ + message: string; + /** + * Status Code + * @default 0 + */ + status_code: number; + data?: components["schemas"]["UsageEventItem"] | null; + /** Message Key */ + message_key?: string | null; + }; + /** APIResponse[UsageEventSummaryResponse] */ + APIResponse_UsageEventSummaryResponse_: { + /** Status */ + status: string; + /** Message */ + message: string; + /** + * Status Code + * @default 0 + */ + status_code: number; + data?: components["schemas"]["UsageEventSummaryResponse"] | null; + /** Message Key */ + message_key?: string | null; + }; + /** APIResponse[UsageEventsResponse] */ + APIResponse_UsageEventsResponse_: { + /** Status */ + status: string; + /** Message */ + message: string; + /** + * Status Code + * @default 0 + */ + status_code: number; + data?: components["schemas"]["UsageEventsResponse"] | null; + /** Message Key */ + message_key?: string | null; + }; + /** APIResponse[dict] */ + APIResponse_dict_: { + /** Status */ + status: string; + /** Message */ + message: string; + /** + * Status Code + * @default 0 + */ + status_code: number; + /** Data */ + data?: { + [key: string]: unknown; + } | null; + /** Message Key */ + message_key?: string | null; + }; + /** CreditsLedgerItem */ + CreditsLedgerItem: { + /** Id */ + id: string; + /** Entry Type */ + entry_type: string; + /** Amount Credits */ + amount_credits: number; + /** Source System */ + source_system: string; + /** Source Ref Type */ + source_ref_type?: string | null; + /** Source Ref Id */ + source_ref_id?: string | null; + /** Pre Settlement Bill */ + pre_settlement_bill?: { + [key: string]: unknown; + } | null; + /** Settlement Result */ + settlement_result?: { + [key: string]: unknown; + } | null; + /** Balance Before */ + balance_before?: { + [key: string]: unknown; + } | null; + /** Balance After */ + balance_after?: { + [key: string]: unknown; + } | null; + /** Ledger Metadata */ + ledger_metadata?: { + [key: string]: unknown; + } | null; + /** Description */ + description?: string | null; + /** + * Created At + * Format: date-time + */ + created_at: string; + }; + /** CreditsLedgerReadinessEntity */ + CreditsLedgerReadinessEntity: { + /** Entity Type */ + entity_type: string; + /** Legacy Count */ + legacy_count: number; + /** Legacy Amount Credits */ + legacy_amount_credits: number; + /** Ledger Count */ + ledger_count: number; + /** Ledger Amount Credits */ + ledger_amount_credits: number; + /** Missing Count */ + missing_count: number; + /** Extra Count */ + extra_count: number; + /** Amount Delta Credits */ + amount_delta_credits: number; + /** Ready */ + ready: boolean; + }; + /** CreditsLedgerReadinessResponse */ + CreditsLedgerReadinessResponse: { + /** + * Checked At + * Format: date-time + */ + checked_at: string; + /** Start Date */ + start_date?: string | null; + /** End Date */ + end_date?: string | null; + /** Scope */ + scope: string; + /** Recommended Read Mode */ + recommended_read_mode: string; + /** Ready */ + ready: boolean; + /** Legacy Total Count */ + legacy_total_count: number; + /** Legacy Total Amount Credits */ + legacy_total_amount_credits: number; + /** Ledger Total Count */ + ledger_total_count: number; + /** Ledger Total Amount Credits */ + ledger_total_amount_credits: number; + /** Entities */ + entities: components["schemas"]["CreditsLedgerReadinessEntity"][]; + }; + /** CreditsLedgerResponse */ + CreditsLedgerResponse: { + /** Items */ + items: components["schemas"]["CreditsLedgerItem"][]; + /** Total */ + total: number; + /** Page */ + page: number; + /** Page Size */ + page_size: number; + summary?: components["schemas"]["CreditsLedgerSummary"] | null; + }; + /** CreditsLedgerSummary */ + CreditsLedgerSummary: { + /** Start Date */ + start_date?: string | null; + /** End Date */ + end_date?: string | null; + /** Bucket */ + bucket: string; + /** Total Entries */ + total_entries: number; + /** Consume Count */ + consume_count: number; + /** Grant Count */ + grant_count: number; + /** Consumed Credits */ + consumed_credits: number; + /** Granted Credits */ + granted_credits: number; + /** Net Amount Credits */ + net_amount_credits: number; + /** Max Amount Items */ + max_amount_items: components["schemas"]["CreditsLedgerItem"][]; + /** Buckets */ + buckets: components["schemas"]["CreditsLedgerSummaryBucket"][]; + }; + /** CreditsLedgerSummaryBucket */ + CreditsLedgerSummaryBucket: { + /** + * Bucket Start + * Format: date-time + */ + bucket_start: string; + /** Entry Count */ + entry_count: number; + /** Consume Count */ + consume_count: number; + /** Grant Count */ + grant_count: number; + /** Consumed Credits */ + consumed_credits: number; + /** Granted Credits */ + granted_credits: number; + /** Net Amount Credits */ + net_amount_credits: number; + }; + /** HTTPValidationError */ + HTTPValidationError: { + /** Detail */ + detail?: components["schemas"]["ValidationError"][]; + }; + /** SettlementHistoryItem */ + SettlementHistoryItem: { + /** Id */ + id: string; + /** Source */ + source: string; + /** Entry Type */ + entry_type: string; + /** Amount Credits */ + amount_credits: number; + /** Source System */ + source_system: string; + /** Source Ref Type */ + source_ref_type?: string | null; + /** Source Ref Id */ + source_ref_id?: string | null; + /** Description */ + description?: string | null; + /** Display Target */ + display_target?: string | null; + /** Billing Summary */ + billing_summary?: string | null; + /** Billing Snapshot Status */ + billing_snapshot_status?: string | null; + /** + * Details Available + * @default true + */ + details_available: boolean; + /** + * Created At + * Format: date-time + */ + created_at: string; + }; + /** SettlementHistoryResponse */ + SettlementHistoryResponse: { + /** Items */ + items: components["schemas"]["SettlementHistoryItem"][]; + /** Next Cursor */ + next_cursor?: string | null; + /** Has More */ + has_more: boolean; + /** Page Size */ + page_size: number; + }; + /** TokenVerificationResponse */ + TokenVerificationResponse: { + /** Is Valid */ + is_valid: boolean; + /** Is Expired */ + is_expired: boolean; + /** Payload */ + payload?: { + [key: string]: unknown; + } | null; + }; + /** UsageCreditsSpentResponse */ + UsageCreditsSpentResponse: { + /** Total Credits */ + total_credits: number; + /** Start Date */ + start_date?: string | null; + /** End Date */ + end_date?: string | null; + }; + /** UsageEventItem */ + UsageEventItem: { + /** Id */ + id: string; + /** Event Type */ + event_type: string; + /** Source System */ + source_system: string; + /** Source Ref Type */ + source_ref_type?: string | null; + /** Source Ref Id */ + source_ref_id?: string | null; + /** Session Id */ + session_id?: string | null; + /** Search Id */ + search_id?: string | null; + /** Execution Id */ + execution_id?: string | null; + /** Tool Id */ + tool_id?: string | null; + /** Model */ + model?: string | null; + /** Query */ + query?: string | null; + /** Success */ + success: boolean; + /** Charge Outcome */ + charge_outcome: string; + /** Error Message */ + error_message?: string | null; + /** Duration Ms */ + duration_ms?: number | null; + /** Request Payload */ + request_payload?: { + [key: string]: unknown; + } | null; + /** Response Payload Summary */ + response_payload_summary?: { + [key: string]: unknown; + } | null; + /** Execution Outcome */ + execution_outcome?: { + [key: string]: unknown; + } | null; + /** Outcome Schema Version */ + outcome_schema_version?: string | null; + /** Transport Success */ + transport_success?: boolean | null; + /** Provider Success */ + provider_success?: boolean | null; + /** Result Valid */ + result_valid?: boolean | null; + /** Billable Success */ + billable_success?: boolean | null; + /** Outcome */ + outcome?: string | null; + /** Outcome Status */ + outcome_status?: string | null; + /** Reason Code */ + reason_code?: string | null; + /** Outcome Message */ + outcome_message?: string | null; + /** Provider Status Code */ + provider_status_code?: string | null; + /** Provider Status Message */ + provider_status_message?: string | null; + /** Http Status Code */ + http_status_code?: number | null; + /** Valid Result Count */ + valid_result_count?: number | null; + /** Raw Result Count */ + raw_result_count?: number | null; + /** Chargeable Quantity */ + chargeable_quantity?: number | null; + /** Retryable */ + retryable?: boolean | null; + /** Display Severity */ + display_severity?: string | null; + /** Billing Snapshot Status */ + billing_snapshot_status?: string | null; + /** Billing Rule Snapshot */ + billing_rule_snapshot?: { + [key: string]: unknown; + } | null; + /** Pre Settlement Bill */ + pre_settlement_bill?: { + [key: string]: unknown; + } | null; + /** Settlement Result */ + settlement_result?: { + [key: string]: unknown; + } | null; + /** Requested Amount Credits */ + requested_amount_credits?: number | null; + /** Actual Amount Credits */ + actual_amount_credits?: number | null; + /** Credits Ledger Entry Id */ + credits_ledger_entry_id?: string | null; + /** Display Target */ + display_target?: string | null; + /** Billing Unit */ + billing_unit?: string | null; + /** Unit Price Credits */ + unit_price_credits?: number | null; + /** Quantity */ + quantity?: number | null; + /** List Amount Credits */ + list_amount_credits?: number | null; + /** Minimum Charge Credits */ + minimum_charge_credits?: number | null; + /** Pricing Profile Id */ + pricing_profile_id?: string | null; + /** Billing Summary */ + billing_summary?: string | null; + /** Pre Settlement Amount Credits */ + pre_settlement_amount_credits?: number | null; + /** Settled Amount Credits */ + settled_amount_credits?: number | null; + /** Pricing Context Id */ + pricing_context_id?: string | null; + /** Harbor Snapshot Id */ + harbor_snapshot_id?: string | null; + /** Harbor Snapshot Version */ + harbor_snapshot_version?: string | null; + /** Resolver Version */ + resolver_version?: string | null; + /** + * Created At + * Format: date-time + */ + created_at: string; + }; + /** UsageEventSummaryItem */ + UsageEventSummaryItem: { + /** Id */ + id: string; + /** Event Type */ + event_type: string; + /** Source System */ + source_system: string; + /** Source Ref Type */ + source_ref_type?: string | null; + /** Source Ref Id */ + source_ref_id?: string | null; + /** Session Id */ + session_id?: string | null; + /** Search Id */ + search_id?: string | null; + /** Execution Id */ + execution_id?: string | null; + /** Tool Id */ + tool_id?: string | null; + /** Model */ + model?: string | null; + /** Query */ + query?: string | null; + /** Success */ + success: boolean; + /** Charge Outcome */ + charge_outcome: string; + /** Error Message */ + error_message?: string | null; + /** Duration Ms */ + duration_ms?: number | null; + /** Transport Success */ + transport_success?: boolean | null; + /** Provider Success */ + provider_success?: boolean | null; + /** Result Valid */ + result_valid?: boolean | null; + /** Billable Success */ + billable_success?: boolean | null; + /** Outcome */ + outcome?: string | null; + /** Outcome Status */ + outcome_status?: string | null; + /** Reason Code */ + reason_code?: string | null; + /** Outcome Message */ + outcome_message?: string | null; + /** Provider Status Code */ + provider_status_code?: string | null; + /** Provider Status Message */ + provider_status_message?: string | null; + /** Http Status Code */ + http_status_code?: number | null; + /** Valid Result Count */ + valid_result_count?: number | null; + /** Raw Result Count */ + raw_result_count?: number | null; + /** Chargeable Quantity */ + chargeable_quantity?: number | null; + /** Retryable */ + retryable?: boolean | null; + /** Display Severity */ + display_severity?: string | null; + /** Billing Snapshot Status */ + billing_snapshot_status?: string | null; + /** Requested Amount Credits */ + requested_amount_credits?: number | null; + /** Actual Amount Credits */ + actual_amount_credits?: number | null; + /** Credits Ledger Entry Id */ + credits_ledger_entry_id?: string | null; + /** Display Target */ + display_target?: string | null; + /** Billing Unit */ + billing_unit?: string | null; + /** Unit Price Credits */ + unit_price_credits?: number | null; + /** Quantity */ + quantity?: number | null; + /** List Amount Credits */ + list_amount_credits?: number | null; + /** Minimum Charge Credits */ + minimum_charge_credits?: number | null; + /** Pricing Profile Id */ + pricing_profile_id?: string | null; + /** Billing Summary */ + billing_summary?: string | null; + /** Pre Settlement Amount Credits */ + pre_settlement_amount_credits?: number | null; + /** Settled Amount Credits */ + settled_amount_credits?: number | null; + /** Pricing Context Id */ + pricing_context_id?: string | null; + /** Harbor Snapshot Id */ + harbor_snapshot_id?: string | null; + /** Harbor Snapshot Version */ + harbor_snapshot_version?: string | null; + /** Resolver Version */ + resolver_version?: string | null; + /** + * Details Available + * @default true + */ + details_available: boolean; + /** + * Created At + * Format: date-time + */ + created_at: string; + }; + /** UsageEventSummaryResponse */ + UsageEventSummaryResponse: { + /** Items */ + items: components["schemas"]["UsageEventSummaryItem"][]; + /** Next Cursor */ + next_cursor?: string | null; + /** Has More */ + has_more: boolean; + /** Page Size */ + page_size: number; + }; + /** UsageEventsResponse */ + UsageEventsResponse: { + /** Items */ + items: components["schemas"]["UsageEventItem"][]; + /** Total */ + total: number; + /** Page */ + page: number; + /** Page Size */ + page_size: number; + summary?: components["schemas"]["UsageEventsSummary"] | null; + }; + /** UsageEventsSummary */ + UsageEventsSummary: { + /** Start Date */ + start_date?: string | null; + /** End Date */ + end_date?: string | null; + /** Bucket */ + bucket: string; + /** Total Count */ + total_count: number; + /** Success Count */ + success_count: number; + /** Failure Count */ + failure_count: number; + /** Charge Outcome Counts */ + charge_outcome_counts: { + [key: string]: number; + }; + /** Pre Settlement Credits */ + pre_settlement_credits: number; + /** Settled Credits */ + settled_credits: number; + /** Max Charge Items */ + max_charge_items: components["schemas"]["UsageEventItem"][]; + /** Buckets */ + buckets: components["schemas"]["UsageEventsSummaryBucket"][]; + }; + /** UsageEventsSummaryBucket */ + UsageEventsSummaryBucket: { + /** + * Bucket Start + * Format: date-time + */ + bucket_start: string; + /** Total Count */ + total_count: number; + /** Success Count */ + success_count: number; + /** Failure Count */ + failure_count: number; + /** Charged Count */ + charged_count: number; + /** Included Count */ + included_count: number; + /** Failed Not Charged Count */ + failed_not_charged_count: number; + /** Failed Charged Review Count */ + failed_charged_review_count: number; + /** Pre Settlement Credits */ + pre_settlement_credits: number; + /** Settled Credits */ + settled_credits: number; + }; + /** ValidationError */ + ValidationError: { + /** Location */ + loc: (string | number)[]; + /** Message */ + msg: string; + /** Error Type */ + type: string; + /** Input */ + input?: unknown; + /** Context */ + ctx?: Record; + }; + /** PublicApiError */ + PublicApiError: { + status?: string; + status_code?: number; + message?: string; + error?: string; + error_message?: string; + remaining_credits?: number | null; + search_id?: string | null; + execution_id?: string | null; + total?: number; + results?: Record[]; + } & { + [key: string]: unknown; + }; + /** PublicSearchRequest */ + PublicSearchRequest: { + /** @description Natural-language capability query. */ + query: string; + /** + * @description Maximum number of ranked results to return. + * @default 20 + */ + limit: number; + /** @description Optional tracking and pricing-context identifier. It does not promise a cache hit. */ + session_id?: string; + }; + /** PublicToolsByIdsRequest */ + PublicToolsByIdsRequest: { + /** @description Capability ids returned by Discover. */ + tool_ids: string[]; + /** @description Search id that returned these capabilities, when available. */ + search_id?: string; + /** @description Optional tracking and pricing-context identifier. It does not promise a cache hit. */ + session_id?: string; + }; + /** PublicExecuteToolRequest */ + PublicExecuteToolRequest: { + /** @description Capability id. Optional when supplied as the `tool_id` query parameter. */ + tool_id?: string; + /** @description Search id that returned the selected capability. */ + search_id?: string; + /** @description Optional tracking and pricing-context identifier. If omitted, the service may use the execution id. */ + session_id?: string; + /** @description Capability-specific parameters validated by the selected tool schema. */ + parameters: { + [key: string]: unknown; + }; + /** + * @description Maximum response payload bytes before truncation. Use -1 for no limit. + * @default 20480 + */ + max_response_size: number; + }; + /** PublicToolParameter */ + PublicToolParameter: { + name: string; + type: string; + required: boolean; + description?: string; + enum?: string[]; + }; + /** PublicToolStats */ + PublicToolStats: { + avg_execution_time_ms?: number; + success_rate?: number; + }; + /** + * PublicBillingRule + * @description Capability billing rule used for pre-call estimation. Final settlement is reflected in usage audit and credits ledger endpoints. + */ + PublicBillingRule: { + [key: string]: unknown; + }; + /** PublicCapabilityResult */ + PublicCapabilityResult: { + tool_id: string; + name?: string; + tool_name?: string; + description?: string; + provider_id?: string; + provider_name?: string; + provider_description?: string; + category?: string; + region?: string; + score?: number; + params?: components["schemas"]["PublicToolParameter"][]; + examples?: { + [key: string]: unknown; + }; + stats?: components["schemas"]["PublicToolStats"]; + /** @description Pre-call cost estimate returned by Discover/Inspect when available. */ + expected_cost?: string; + /** @description Legacy cost estimate. Prefer `expected_cost` and `billing_rule` for pre-call display. */ + cost?: number | string; + billing_rule?: components["schemas"]["PublicBillingRule"]; + calls_count?: string; + } & { + [key: string]: unknown; + }; + /** PublicSearchResponse */ + PublicSearchResponse: { + query?: string; + search_id: string; + total: number; + results: components["schemas"]["PublicCapabilityResult"][]; + elapsed_time_ms?: number; + remaining_credits?: number | null; + error_message?: string | null; + } & { + [key: string]: unknown; + }; + /** PublicExecuteResult */ + PublicExecuteResult: { + data?: { + [key: string]: unknown; + }; + message?: string; + truncated_content?: string; + /** Format: uri */ + full_content_file_url?: string; + content_schema?: { + [key: string]: unknown; + }; + } & { + [key: string]: unknown; + }; + /** PublicCompactBillingStatement */ + PublicCompactBillingStatement: { + summary?: string; + list_amount_credits?: number; + final_amount_credits?: number; + } & { + [key: string]: unknown; + }; + /** PublicExecuteToolResponse */ + PublicExecuteToolResponse: { + execution_id: string; + result: components["schemas"]["PublicExecuteResult"]; + success: boolean; + error_message?: string | null; + execution_time?: number; + elapsed_time_ms?: number; + billing?: components["schemas"]["PublicCompactBillingStatement"]; + cost?: number; + remaining_credits?: number | null; + } & { + [key: string]: unknown; + }; + }; + responses: never; + parameters: never; + requestBodies: never; + headers: never; + pathItems: never; +} +export type $defs = Record; +export interface operations { + get_account_settlement_history_api_v1_auth_account_settlement_history_get: { + parameters: { + query?: { + /** @description 开始日期 (YYYY-MM-DD) */ + start_date?: string | null; + /** @description 结束日期 (YYYY-MM-DD) */ + end_date?: string | null; + /** @description 账本范围 */ + scope?: string; + /** @description 下一页游标 */ + cursor?: string | null; + /** @description 每页数量 */ + page_size?: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["APIResponse_SettlementHistoryResponse_"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + get_current_user_credits_api_v1_auth_credits_get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["APIResponse_dict_"]; + }; + }; + }; + }; + get_credits_ledger_api_v1_auth_credits_ledger_get: { + parameters: { + query?: { + /** + * @description 开始日期 (YYYY-MM-DD) + * @example 2026-05-01 + */ + start_date?: string | null; + /** + * @description 结束日期 (YYYY-MM-DD) + * @example 2026-05-16 + */ + end_date?: string | null; + /** + * @description 账本事件类型 + * @example consume_tool_execute + */ + entry_type?: string | null; + /** + * @description 预定义账本范围,例如 account_history + * @example account_history + */ + scope?: string | null; + /** + * @description 页码 + * @example 1 + */ + page?: number; + /** + * @description 每页数量 + * @example 50 + */ + page_size?: number; + /** @example 1 */ + min_credits?: number | null; + /** @example 25 */ + max_credits?: number | null; + /** @example consume */ + direction?: string; + /** @example day */ + bucket?: string | null; + /** @example true */ + summary?: boolean; + /** @example 5 */ + limit?: number | null; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["APIResponse_CreditsLedgerResponse_"]; + }; + }; + /** @description Invalid ledger query */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["APIResponse_dict_"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + get_credits_ledger_readiness_api_v1_auth_credits_ledger_readiness_get: { + parameters: { + query?: { + /** @description 开始日期 (YYYY-MM-DD) */ + start_date?: string | null; + /** @description 结束日期 (YYYY-MM-DD) */ + end_date?: string | null; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["APIResponse_CreditsLedgerReadinessResponse_"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + export_credits_ledger_api_v1_auth_credits_ledger_export_get: { + parameters: { + query?: { + /** @description 开始日期 (YYYY-MM-DD) */ + start_date?: string | null; + /** @description 结束日期 (YYYY-MM-DD) */ + end_date?: string | null; + /** @description 账本导出范围 */ + scope?: string | null; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description CSV export. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "text/csv": string; + }; + }; + }; + }; + get_credits_ledger_entry_api_v1_auth_credits_ledger__entry_id__get: { + parameters: { + query?: never; + header?: never; + path: { + entry_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["APIResponse_CreditsLedgerItem_"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + get_usage_credits_spent_api_v1_auth_usage_credits_spent_get: { + parameters: { + query?: { + /** @description 开始日期 (YYYY-MM-DD) */ + start_date?: string | null; + /** @description 结束日期 (YYYY-MM-DD) */ + end_date?: string | null; + /** @description 事件分组过滤 */ + kind?: string | null; + /** @description 收费结果过滤 */ + charge_outcome?: string | null; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["APIResponse_UsageCreditsSpentResponse_"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + get_usage_history_v2_api_v1_auth_usage_history_v2_get: { + parameters: { + query?: { + /** + * @description 开始日期 (YYYY-MM-DD) + * @example 2026-05-01 + */ + start_date?: string | null; + /** + * @description 结束日期 (YYYY-MM-DD) + * @example 2026-05-16 + */ + end_date?: string | null; + /** + * @description 事件类型过滤 + * @example tool_execute + */ + event_type?: string | null; + /** + * @description 事件分组过滤 + * @example call + */ + kind?: string | null; + /** + * @description 是否成功 + * @example true + */ + success?: boolean | null; + /** + * @description 是否可计费 + * @example true + */ + billable_success?: boolean | null; + /** + * @description 执行 outcome 过滤 + * @example completed + */ + outcome?: string | null; + /** + * @description 执行 reason_code 过滤 + * @example provider_success + */ + reason_code?: string | null; + /** + * @description 是否存在 execution_outcome + * @example true + */ + has_execution_outcome?: boolean | null; + /** + * @description 收费结果过滤 + * @example charged + */ + charge_outcome?: string | null; + /** + * @description 异常类型过滤 + * @example missing_ledger_link + */ + anomaly?: string | null; + /** + * @description search_id 聚焦过滤 + * @example srch_01HZX9QK7J3M9T + */ + search_id?: string | null; + /** + * @description execution_id 聚焦过滤 + * @example exec_01HZX9R2R4S2E + */ + execution_id?: string | null; + /** + * @description 页码 + * @example 1 + */ + page?: number; + /** + * @description 每页数量 + * @example 50 + */ + page_size?: number; + /** @example 1 */ + min_credits?: number | null; + /** @example 25 */ + max_credits?: number | null; + /** @example day */ + bucket?: string | null; + /** @example true */ + summary?: boolean; + /** @example 5 */ + limit?: number | null; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["APIResponse_UsageEventsResponse_"]; + }; + }; + /** @description Invalid audit query */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["APIResponse_dict_"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + export_usage_history_v2_api_v1_auth_usage_history_v2_export_get: { + parameters: { + query?: { + /** @description 开始日期 (YYYY-MM-DD) */ + start_date?: string | null; + /** @description 结束日期 (YYYY-MM-DD) */ + end_date?: string | null; + /** @description 事件类型过滤 */ + event_type?: string | null; + /** @description 事件分组过滤 */ + kind?: string | null; + /** @description 是否成功 */ + success?: boolean | null; + /** @description 是否可计费 */ + billable_success?: boolean | null; + /** @description 执行 outcome 过滤 */ + outcome?: string | null; + /** @description 执行 reason_code 过滤 */ + reason_code?: string | null; + /** @description 是否存在 execution_outcome */ + has_execution_outcome?: boolean | null; + /** @description 收费结果过滤 */ + charge_outcome?: string | null; + /** @description 异常类型过滤 */ + anomaly?: string | null; + /** @description search_id 聚焦过滤 */ + search_id?: string | null; + /** @description execution_id 聚焦过滤 */ + execution_id?: string | null; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description CSV export. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "text/csv": string; + }; + }; + }; + }; + get_usage_history_v2_summary_api_v1_auth_usage_history_v2_summary_get: { + parameters: { + query?: { + /** @description 开始日期 (YYYY-MM-DD) */ + start_date?: string | null; + /** @description 结束日期 (YYYY-MM-DD) */ + end_date?: string | null; + /** @description 事件类型过滤 */ + event_type?: string | null; + /** @description 事件分组过滤 */ + kind?: string | null; + /** @description 是否成功 */ + success?: boolean | null; + /** @description 是否可计费 */ + billable_success?: boolean | null; + /** @description 执行 outcome 过滤 */ + outcome?: string | null; + /** @description 执行 reason_code 过滤 */ + reason_code?: string | null; + /** @description 是否存在 execution_outcome */ + has_execution_outcome?: boolean | null; + /** @description 收费结果过滤 */ + charge_outcome?: string | null; + /** @description 异常类型过滤 */ + anomaly?: string | null; + /** @description search_id 聚焦过滤 */ + search_id?: string | null; + /** @description execution_id 聚焦过滤 */ + execution_id?: string | null; + /** @description 下一页游标 */ + cursor?: string | null; + /** @description 每页数量 */ + page_size?: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["APIResponse_UsageEventSummaryResponse_"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + get_usage_history_v2_entry_api_v1_auth_usage_history_v2__event_id__get: { + parameters: { + query?: never; + header?: never; + path: { + event_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["APIResponse_UsageEventItem_"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + verify_token_api_v1_auth_verify_token_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["APIResponse_TokenVerificationResponse_"]; + }; + }; + }; + }; + get_current_user_credits_api_v1_credits_get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["APIResponse_dict_"]; + }; + }; + }; + }; + get_providers_api_v1_providers_get: { + parameters: { + query?: { + categories?: string | null; + skip?: number; + limit?: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + get_provider_categories_api_v1_providers_categories_get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + search_api_v1_search_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + /** @example { + * "query": "weather forecast API", + * "limit": 10, + * "session_id": "sess_7Q9m" + * } */ + "application/json": components["schemas"]["PublicSearchRequest"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + /** @description Maximum requests allowed in the current rate-limit window. */ + "X-RateLimit-Limit"?: number; + /** @description Requests remaining in the current rate-limit window. */ + "X-RateLimit-Remaining"?: number; + /** @description Unix epoch seconds when the current rate-limit window resets. */ + "X-RateLimit-Reset"?: number; + /** @description Seconds until the client should retry after a rate-limit response. */ + "Retry-After"?: number; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PublicSearchResponse"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + /** @description Maximum requests allowed in the current rate-limit window. */ + "X-RateLimit-Limit"?: number; + /** @description Requests remaining in the current rate-limit window. */ + "X-RateLimit-Remaining"?: number; + /** @description Unix epoch seconds when the current rate-limit window resets. */ + "X-RateLimit-Reset"?: number; + /** @description Seconds until the client should retry after a rate-limit response. */ + "Retry-After"?: number; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PublicApiError"]; + }; + }; + /** @description Insufficient credits */ + 402: { + headers: { + /** @description Maximum requests allowed in the current rate-limit window. */ + "X-RateLimit-Limit"?: number; + /** @description Requests remaining in the current rate-limit window. */ + "X-RateLimit-Remaining"?: number; + /** @description Unix epoch seconds when the current rate-limit window resets. */ + "X-RateLimit-Reset"?: number; + /** @description Seconds until the client should retry after a rate-limit response. */ + "Retry-After"?: number; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PublicSearchResponse"]; + }; + }; + /** @description Too Many Requests */ + 429: { + headers: { + /** @description Maximum requests allowed in the current rate-limit window. */ + "X-RateLimit-Limit"?: number; + /** @description Requests remaining in the current rate-limit window. */ + "X-RateLimit-Remaining"?: number; + /** @description Unix epoch seconds when the current rate-limit window resets. */ + "X-RateLimit-Reset"?: number; + /** @description Seconds until the client should retry after a rate-limit response. */ + "Retry-After"?: number; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PublicApiError"]; + }; + }; + }; + }; + get_tools_by_ids_api_v1_tools_by_ids_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + /** @example { + * "tool_ids": [ + * "openweathermap.weather.execute.v1" + * ], + * "search_id": "srch_01HZX9QK7J3M9T", + * "session_id": "sess_7Q9m" + * } */ + "application/json": components["schemas"]["PublicToolsByIdsRequest"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PublicSearchResponse"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PublicApiError"]; + }; + }; + /** @description Upstream timeout */ + 504: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PublicApiError"]; + }; + }; + }; + }; + execute_api_v1_tools_execute_post: { + parameters: { + query?: { + /** @description Tool ID (optional if provided in request body) */ + tool_id?: string | null; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + /** @example { + * "search_id": "srch_01HZX9QK7J3M9T", + * "session_id": "sess_7Q9m", + * "parameters": { + * "city": "London", + * "units": "metric" + * }, + * "max_response_size": 20480 + * } */ + "application/json": components["schemas"]["PublicExecuteToolRequest"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + /** @description Maximum requests allowed in the current rate-limit window. */ + "X-RateLimit-Limit"?: number; + /** @description Requests remaining in the current rate-limit window. */ + "X-RateLimit-Remaining"?: number; + /** @description Unix epoch seconds when the current rate-limit window resets. */ + "X-RateLimit-Reset"?: number; + /** @description Seconds until the client should retry after a rate-limit response. */ + "Retry-After"?: number; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PublicExecuteToolResponse"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + /** @description Maximum requests allowed in the current rate-limit window. */ + "X-RateLimit-Limit"?: number; + /** @description Requests remaining in the current rate-limit window. */ + "X-RateLimit-Remaining"?: number; + /** @description Unix epoch seconds when the current rate-limit window resets. */ + "X-RateLimit-Reset"?: number; + /** @description Seconds until the client should retry after a rate-limit response. */ + "Retry-After"?: number; + [name: string]: unknown; + }; + content?: never; + }; + /** @description Insufficient credits */ + 402: { + headers: { + /** @description Maximum requests allowed in the current rate-limit window. */ + "X-RateLimit-Limit"?: number; + /** @description Requests remaining in the current rate-limit window. */ + "X-RateLimit-Remaining"?: number; + /** @description Unix epoch seconds when the current rate-limit window resets. */ + "X-RateLimit-Reset"?: number; + /** @description Seconds until the client should retry after a rate-limit response. */ + "Retry-After"?: number; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PublicExecuteToolResponse"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PublicExecuteToolResponse"]; + }; + }; + /** @description Too Many Requests */ + 429: { + headers: { + /** @description Maximum requests allowed in the current rate-limit window. */ + "X-RateLimit-Limit"?: number; + /** @description Requests remaining in the current rate-limit window. */ + "X-RateLimit-Remaining"?: number; + /** @description Unix epoch seconds when the current rate-limit window resets. */ + "X-RateLimit-Reset"?: number; + /** @description Seconds until the client should retry after a rate-limit response. */ + "Retry-After"?: number; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PublicApiError"]; + }; + }; + }; + }; +} diff --git a/packages/python-sdk/pyproject.toml b/packages/python-sdk/pyproject.toml index 74acd47..3db20d0 100644 --- a/packages/python-sdk/pyproject.toml +++ b/packages/python-sdk/pyproject.toml @@ -24,6 +24,9 @@ dev = [ "pytest-asyncio", "python-dotenv", "rich>=13.0.0", + # Pinned so regenerated models stay byte-stable with the checked-in + # qveris/generated/openapi_models.py and the drift CI (issue #37 phase 2). + "datamodel-code-generator==0.26.3", ] [tool.hatch.build.targets.wheel] diff --git a/packages/python-sdk/qveris/generated/__init__.py b/packages/python-sdk/qveris/generated/__init__.py new file mode 100644 index 0000000..9e7be83 --- /dev/null +++ b/packages/python-sdk/qveris/generated/__init__.py @@ -0,0 +1,17 @@ +"""Generated OpenAPI contract models (issue #37, phase 2). + +`openapi_models.py` is generated from the website-mirrored +`docs/openapi/qveris-public-api.openapi.json` by `datamodel-code-generator` +(pinned in the ``dev`` extra). It is a **contract reference**, not the public +SDK surface — the hand-written models in ``qveris.types`` remain the public +API. Do not edit the generated file by hand; regenerate instead: + + python -m datamodel_code_generator \\ + --input ../../docs/openapi/qveris-public-api.openapi.json \\ + --input-file-type openapi \\ + --output qveris/generated/openapi_models.py \\ + --output-model-type pydantic_v2.BaseModel \\ + --target-python-version 3.8 --use-schema-description --disable-timestamp + +CI re-runs this and fails on `git diff --exit-code` to catch contract drift. +""" diff --git a/packages/python-sdk/qveris/generated/openapi_models.py b/packages/python-sdk/qveris/generated/openapi_models.py new file mode 100644 index 0000000..1670cf0 --- /dev/null +++ b/packages/python-sdk/qveris/generated/openapi_models.py @@ -0,0 +1,572 @@ +# generated by datamodel-codegen: +# filename: qveris-public-api.openapi.json + +from __future__ import annotations + +from datetime import datetime +from typing import Any, Dict, List, Optional, Union + +from pydantic import AnyUrl, BaseModel, ConfigDict, Field, conint, constr + + +class APIResponseDict(BaseModel): + status: str = Field(..., title='Status') + message: str = Field(..., title='Message') + status_code: Optional[int] = Field(0, title='Status Code') + data: Optional[Dict[str, Any]] = Field(None, title='Data') + message_key: Optional[str] = Field(None, title='Message Key') + + +class CreditsLedgerItem(BaseModel): + id: str = Field(..., title='Id') + entry_type: str = Field(..., title='Entry Type') + amount_credits: float = Field(..., title='Amount Credits') + source_system: str = Field(..., title='Source System') + source_ref_type: Optional[str] = Field(None, title='Source Ref Type') + source_ref_id: Optional[str] = Field(None, title='Source Ref Id') + pre_settlement_bill: Optional[Dict[str, Any]] = Field( + None, title='Pre Settlement Bill' + ) + settlement_result: Optional[Dict[str, Any]] = Field(None, title='Settlement Result') + balance_before: Optional[Dict[str, Any]] = Field(None, title='Balance Before') + balance_after: Optional[Dict[str, Any]] = Field(None, title='Balance After') + ledger_metadata: Optional[Dict[str, Any]] = Field(None, title='Ledger Metadata') + description: Optional[str] = Field(None, title='Description') + created_at: datetime = Field(..., title='Created At') + + +class CreditsLedgerReadinessEntity(BaseModel): + entity_type: str = Field(..., title='Entity Type') + legacy_count: int = Field(..., title='Legacy Count') + legacy_amount_credits: float = Field(..., title='Legacy Amount Credits') + ledger_count: int = Field(..., title='Ledger Count') + ledger_amount_credits: float = Field(..., title='Ledger Amount Credits') + missing_count: int = Field(..., title='Missing Count') + extra_count: int = Field(..., title='Extra Count') + amount_delta_credits: float = Field(..., title='Amount Delta Credits') + ready: bool = Field(..., title='Ready') + + +class CreditsLedgerReadinessResponse(BaseModel): + checked_at: datetime = Field(..., title='Checked At') + start_date: Optional[datetime] = Field(None, title='Start Date') + end_date: Optional[datetime] = Field(None, title='End Date') + scope: str = Field(..., title='Scope') + recommended_read_mode: str = Field(..., title='Recommended Read Mode') + ready: bool = Field(..., title='Ready') + legacy_total_count: int = Field(..., title='Legacy Total Count') + legacy_total_amount_credits: float = Field(..., title='Legacy Total Amount Credits') + ledger_total_count: int = Field(..., title='Ledger Total Count') + ledger_total_amount_credits: float = Field(..., title='Ledger Total Amount Credits') + entities: List[CreditsLedgerReadinessEntity] = Field(..., title='Entities') + + +class CreditsLedgerSummaryBucket(BaseModel): + bucket_start: datetime = Field(..., title='Bucket Start') + entry_count: int = Field(..., title='Entry Count') + consume_count: int = Field(..., title='Consume Count') + grant_count: int = Field(..., title='Grant Count') + consumed_credits: float = Field(..., title='Consumed Credits') + granted_credits: float = Field(..., title='Granted Credits') + net_amount_credits: float = Field(..., title='Net Amount Credits') + + +class SettlementHistoryItem(BaseModel): + id: str = Field(..., title='Id') + source: str = Field(..., title='Source') + entry_type: str = Field(..., title='Entry Type') + amount_credits: float = Field(..., title='Amount Credits') + source_system: str = Field(..., title='Source System') + source_ref_type: Optional[str] = Field(None, title='Source Ref Type') + source_ref_id: Optional[str] = Field(None, title='Source Ref Id') + description: Optional[str] = Field(None, title='Description') + display_target: Optional[str] = Field(None, title='Display Target') + billing_summary: Optional[str] = Field(None, title='Billing Summary') + billing_snapshot_status: Optional[str] = Field( + None, title='Billing Snapshot Status' + ) + details_available: Optional[bool] = Field(True, title='Details Available') + created_at: datetime = Field(..., title='Created At') + + +class SettlementHistoryResponse(BaseModel): + items: List[SettlementHistoryItem] = Field(..., title='Items') + next_cursor: Optional[str] = Field(None, title='Next Cursor') + has_more: bool = Field(..., title='Has More') + page_size: int = Field(..., title='Page Size') + + +class TokenVerificationResponse(BaseModel): + is_valid: bool = Field(..., title='Is Valid') + is_expired: bool = Field(..., title='Is Expired') + payload: Optional[Dict[str, Any]] = Field(None, title='Payload') + + +class UsageCreditsSpentResponse(BaseModel): + total_credits: float = Field(..., title='Total Credits') + start_date: Optional[str] = Field(None, title='Start Date') + end_date: Optional[str] = Field(None, title='End Date') + + +class UsageEventItem(BaseModel): + id: str = Field(..., title='Id') + event_type: str = Field(..., title='Event Type') + source_system: str = Field(..., title='Source System') + source_ref_type: Optional[str] = Field(None, title='Source Ref Type') + source_ref_id: Optional[str] = Field(None, title='Source Ref Id') + session_id: Optional[str] = Field(None, title='Session Id') + search_id: Optional[str] = Field(None, title='Search Id') + execution_id: Optional[str] = Field(None, title='Execution Id') + tool_id: Optional[str] = Field(None, title='Tool Id') + model: Optional[str] = Field(None, title='Model') + query: Optional[str] = Field(None, title='Query') + success: bool = Field(..., title='Success') + charge_outcome: str = Field(..., title='Charge Outcome') + error_message: Optional[str] = Field(None, title='Error Message') + duration_ms: Optional[float] = Field(None, title='Duration Ms') + request_payload: Optional[Dict[str, Any]] = Field(None, title='Request Payload') + response_payload_summary: Optional[Dict[str, Any]] = Field( + None, title='Response Payload Summary' + ) + execution_outcome: Optional[Dict[str, Any]] = Field(None, title='Execution Outcome') + outcome_schema_version: Optional[str] = Field(None, title='Outcome Schema Version') + transport_success: Optional[bool] = Field(None, title='Transport Success') + provider_success: Optional[bool] = Field(None, title='Provider Success') + result_valid: Optional[bool] = Field(None, title='Result Valid') + billable_success: Optional[bool] = Field(None, title='Billable Success') + outcome: Optional[str] = Field(None, title='Outcome') + outcome_status: Optional[str] = Field(None, title='Outcome Status') + reason_code: Optional[str] = Field(None, title='Reason Code') + outcome_message: Optional[str] = Field(None, title='Outcome Message') + provider_status_code: Optional[str] = Field(None, title='Provider Status Code') + provider_status_message: Optional[str] = Field( + None, title='Provider Status Message' + ) + http_status_code: Optional[int] = Field(None, title='Http Status Code') + valid_result_count: Optional[int] = Field(None, title='Valid Result Count') + raw_result_count: Optional[int] = Field(None, title='Raw Result Count') + chargeable_quantity: Optional[float] = Field(None, title='Chargeable Quantity') + retryable: Optional[bool] = Field(None, title='Retryable') + display_severity: Optional[str] = Field(None, title='Display Severity') + billing_snapshot_status: Optional[str] = Field( + None, title='Billing Snapshot Status' + ) + billing_rule_snapshot: Optional[Dict[str, Any]] = Field( + None, title='Billing Rule Snapshot' + ) + pre_settlement_bill: Optional[Dict[str, Any]] = Field( + None, title='Pre Settlement Bill' + ) + settlement_result: Optional[Dict[str, Any]] = Field(None, title='Settlement Result') + requested_amount_credits: Optional[float] = Field( + None, title='Requested Amount Credits' + ) + actual_amount_credits: Optional[float] = Field(None, title='Actual Amount Credits') + credits_ledger_entry_id: Optional[str] = Field( + None, title='Credits Ledger Entry Id' + ) + display_target: Optional[str] = Field(None, title='Display Target') + billing_unit: Optional[str] = Field(None, title='Billing Unit') + unit_price_credits: Optional[float] = Field(None, title='Unit Price Credits') + quantity: Optional[float] = Field(None, title='Quantity') + list_amount_credits: Optional[float] = Field(None, title='List Amount Credits') + minimum_charge_credits: Optional[float] = Field( + None, title='Minimum Charge Credits' + ) + pricing_profile_id: Optional[str] = Field(None, title='Pricing Profile Id') + billing_summary: Optional[str] = Field(None, title='Billing Summary') + pre_settlement_amount_credits: Optional[float] = Field( + None, title='Pre Settlement Amount Credits' + ) + settled_amount_credits: Optional[float] = Field( + None, title='Settled Amount Credits' + ) + pricing_context_id: Optional[str] = Field(None, title='Pricing Context Id') + harbor_snapshot_id: Optional[str] = Field(None, title='Harbor Snapshot Id') + harbor_snapshot_version: Optional[str] = Field( + None, title='Harbor Snapshot Version' + ) + resolver_version: Optional[str] = Field(None, title='Resolver Version') + created_at: datetime = Field(..., title='Created At') + + +class UsageEventSummaryItem(BaseModel): + id: str = Field(..., title='Id') + event_type: str = Field(..., title='Event Type') + source_system: str = Field(..., title='Source System') + source_ref_type: Optional[str] = Field(None, title='Source Ref Type') + source_ref_id: Optional[str] = Field(None, title='Source Ref Id') + session_id: Optional[str] = Field(None, title='Session Id') + search_id: Optional[str] = Field(None, title='Search Id') + execution_id: Optional[str] = Field(None, title='Execution Id') + tool_id: Optional[str] = Field(None, title='Tool Id') + model: Optional[str] = Field(None, title='Model') + query: Optional[str] = Field(None, title='Query') + success: bool = Field(..., title='Success') + charge_outcome: str = Field(..., title='Charge Outcome') + error_message: Optional[str] = Field(None, title='Error Message') + duration_ms: Optional[float] = Field(None, title='Duration Ms') + transport_success: Optional[bool] = Field(None, title='Transport Success') + provider_success: Optional[bool] = Field(None, title='Provider Success') + result_valid: Optional[bool] = Field(None, title='Result Valid') + billable_success: Optional[bool] = Field(None, title='Billable Success') + outcome: Optional[str] = Field(None, title='Outcome') + outcome_status: Optional[str] = Field(None, title='Outcome Status') + reason_code: Optional[str] = Field(None, title='Reason Code') + outcome_message: Optional[str] = Field(None, title='Outcome Message') + provider_status_code: Optional[str] = Field(None, title='Provider Status Code') + provider_status_message: Optional[str] = Field( + None, title='Provider Status Message' + ) + http_status_code: Optional[int] = Field(None, title='Http Status Code') + valid_result_count: Optional[int] = Field(None, title='Valid Result Count') + raw_result_count: Optional[int] = Field(None, title='Raw Result Count') + chargeable_quantity: Optional[float] = Field(None, title='Chargeable Quantity') + retryable: Optional[bool] = Field(None, title='Retryable') + display_severity: Optional[str] = Field(None, title='Display Severity') + billing_snapshot_status: Optional[str] = Field( + None, title='Billing Snapshot Status' + ) + requested_amount_credits: Optional[float] = Field( + None, title='Requested Amount Credits' + ) + actual_amount_credits: Optional[float] = Field(None, title='Actual Amount Credits') + credits_ledger_entry_id: Optional[str] = Field( + None, title='Credits Ledger Entry Id' + ) + display_target: Optional[str] = Field(None, title='Display Target') + billing_unit: Optional[str] = Field(None, title='Billing Unit') + unit_price_credits: Optional[float] = Field(None, title='Unit Price Credits') + quantity: Optional[float] = Field(None, title='Quantity') + list_amount_credits: Optional[float] = Field(None, title='List Amount Credits') + minimum_charge_credits: Optional[float] = Field( + None, title='Minimum Charge Credits' + ) + pricing_profile_id: Optional[str] = Field(None, title='Pricing Profile Id') + billing_summary: Optional[str] = Field(None, title='Billing Summary') + pre_settlement_amount_credits: Optional[float] = Field( + None, title='Pre Settlement Amount Credits' + ) + settled_amount_credits: Optional[float] = Field( + None, title='Settled Amount Credits' + ) + pricing_context_id: Optional[str] = Field(None, title='Pricing Context Id') + harbor_snapshot_id: Optional[str] = Field(None, title='Harbor Snapshot Id') + harbor_snapshot_version: Optional[str] = Field( + None, title='Harbor Snapshot Version' + ) + resolver_version: Optional[str] = Field(None, title='Resolver Version') + details_available: Optional[bool] = Field(True, title='Details Available') + created_at: datetime = Field(..., title='Created At') + + +class UsageEventSummaryResponse(BaseModel): + items: List[UsageEventSummaryItem] = Field(..., title='Items') + next_cursor: Optional[str] = Field(None, title='Next Cursor') + has_more: bool = Field(..., title='Has More') + page_size: int = Field(..., title='Page Size') + + +class UsageEventsSummaryBucket(BaseModel): + bucket_start: datetime = Field(..., title='Bucket Start') + total_count: int = Field(..., title='Total Count') + success_count: int = Field(..., title='Success Count') + failure_count: int = Field(..., title='Failure Count') + charged_count: int = Field(..., title='Charged Count') + included_count: int = Field(..., title='Included Count') + failed_not_charged_count: int = Field(..., title='Failed Not Charged Count') + failed_charged_review_count: int = Field(..., title='Failed Charged Review Count') + pre_settlement_credits: float = Field(..., title='Pre Settlement Credits') + settled_credits: float = Field(..., title='Settled Credits') + + +class ValidationError(BaseModel): + loc: List[Union[str, int]] = Field(..., title='Location') + msg: str = Field(..., title='Message') + type: str = Field(..., title='Error Type') + input: Optional[Any] = Field(None, title='Input') + ctx: Optional[Dict[str, Any]] = Field(None, title='Context') + + +class PublicApiError(BaseModel): + model_config = ConfigDict( + extra='allow', + ) + status: Optional[str] = Field(None, examples=['failure']) + status_code: Optional[int] = None + message: Optional[str] = None + error: Optional[str] = None + error_message: Optional[str] = None + remaining_credits: Optional[float] = None + search_id: Optional[str] = None + execution_id: Optional[str] = None + total: Optional[int] = None + results: Optional[List[Dict[str, Any]]] = None + + +class PublicSearchRequest(BaseModel): + query: constr(min_length=1) = Field( + ..., description='Natural-language capability query.' + ) + limit: Optional[conint(ge=1, le=100)] = Field( + 20, description='Maximum number of ranked results to return.' + ) + session_id: Optional[str] = Field( + None, + description='Optional tracking and pricing-context identifier. It does not promise a cache hit.', + ) + + +class PublicToolsByIdsRequest(BaseModel): + tool_ids: List[str] = Field( + ..., description='Capability ids returned by Discover.', min_length=1 + ) + search_id: Optional[str] = Field( + None, description='Search id that returned these capabilities, when available.' + ) + session_id: Optional[str] = Field( + None, + description='Optional tracking and pricing-context identifier. It does not promise a cache hit.', + ) + + +class PublicExecuteToolRequest(BaseModel): + tool_id: Optional[str] = Field( + None, + description='Capability id. Optional when supplied as the `tool_id` query parameter.', + ) + search_id: Optional[str] = Field( + None, description='Search id that returned the selected capability.' + ) + session_id: Optional[str] = Field( + None, + description='Optional tracking and pricing-context identifier. If omitted, the service may use the execution id.', + ) + parameters: Dict[str, Any] = Field( + ..., + description='Capability-specific parameters validated by the selected tool schema.', + ) + max_response_size: Optional[int] = Field( + 20480, + description='Maximum response payload bytes before truncation. Use -1 for no limit.', + ) + + +class PublicToolParameter(BaseModel): + name: str + type: str + required: bool + description: Optional[str] = None + enum: Optional[List[str]] = None + + +class PublicToolStats(BaseModel): + avg_execution_time_ms: Optional[float] = None + success_rate: Optional[float] = None + + +class PublicBillingRule(BaseModel): + """ + Capability billing rule used for pre-call estimation. Final settlement is reflected in usage audit and credits ledger endpoints. + """ + + model_config = ConfigDict( + extra='allow', + ) + + +class PublicCapabilityResult(BaseModel): + model_config = ConfigDict( + extra='allow', + ) + tool_id: str + name: Optional[str] = None + tool_name: Optional[str] = None + description: Optional[str] = None + provider_id: Optional[str] = None + provider_name: Optional[str] = None + provider_description: Optional[str] = None + category: Optional[str] = None + region: Optional[str] = None + score: Optional[float] = None + params: Optional[List[PublicToolParameter]] = None + examples: Optional[Dict[str, Any]] = None + stats: Optional[PublicToolStats] = None + expected_cost: Optional[str] = Field( + None, + description='Pre-call cost estimate returned by Discover/Inspect when available.', + ) + cost: Optional[Union[float, str]] = Field( + None, + description='Legacy cost estimate. Prefer `expected_cost` and `billing_rule` for pre-call display.', + ) + billing_rule: Optional[PublicBillingRule] = None + calls_count: Optional[str] = None + + +class PublicSearchResponse(BaseModel): + model_config = ConfigDict( + extra='allow', + ) + query: Optional[str] = None + search_id: str + total: int + results: List[PublicCapabilityResult] + elapsed_time_ms: Optional[float] = None + remaining_credits: Optional[float] = None + error_message: Optional[str] = None + + +class PublicExecuteResult(BaseModel): + model_config = ConfigDict( + extra='allow', + ) + data: Optional[Dict[str, Any]] = None + message: Optional[str] = None + truncated_content: Optional[str] = None + full_content_file_url: Optional[AnyUrl] = None + content_schema: Optional[Dict[str, Any]] = None + + +class PublicCompactBillingStatement(BaseModel): + model_config = ConfigDict( + extra='allow', + ) + summary: Optional[str] = None + list_amount_credits: Optional[float] = None + final_amount_credits: Optional[float] = None + + +class PublicExecuteToolResponse(BaseModel): + model_config = ConfigDict( + extra='allow', + ) + execution_id: str + result: PublicExecuteResult + success: bool + error_message: Optional[str] = None + execution_time: Optional[float] = None + elapsed_time_ms: Optional[float] = None + billing: Optional[PublicCompactBillingStatement] = None + cost: Optional[float] = None + remaining_credits: Optional[float] = None + + +class APIResponseCreditsLedgerItem(BaseModel): + status: str = Field(..., title='Status') + message: str = Field(..., title='Message') + status_code: Optional[int] = Field(0, title='Status Code') + data: Optional[CreditsLedgerItem] = None + message_key: Optional[str] = Field(None, title='Message Key') + + +class APIResponseCreditsLedgerReadinessResponse(BaseModel): + status: str = Field(..., title='Status') + message: str = Field(..., title='Message') + status_code: Optional[int] = Field(0, title='Status Code') + data: Optional[CreditsLedgerReadinessResponse] = None + message_key: Optional[str] = Field(None, title='Message Key') + + +class APIResponseSettlementHistoryResponse(BaseModel): + status: str = Field(..., title='Status') + message: str = Field(..., title='Message') + status_code: Optional[int] = Field(0, title='Status Code') + data: Optional[SettlementHistoryResponse] = None + message_key: Optional[str] = Field(None, title='Message Key') + + +class APIResponseTokenVerificationResponse(BaseModel): + status: str = Field(..., title='Status') + message: str = Field(..., title='Message') + status_code: Optional[int] = Field(0, title='Status Code') + data: Optional[TokenVerificationResponse] = None + message_key: Optional[str] = Field(None, title='Message Key') + + +class APIResponseUsageCreditsSpentResponse(BaseModel): + status: str = Field(..., title='Status') + message: str = Field(..., title='Message') + status_code: Optional[int] = Field(0, title='Status Code') + data: Optional[UsageCreditsSpentResponse] = None + message_key: Optional[str] = Field(None, title='Message Key') + + +class APIResponseUsageEventItem(BaseModel): + status: str = Field(..., title='Status') + message: str = Field(..., title='Message') + status_code: Optional[int] = Field(0, title='Status Code') + data: Optional[UsageEventItem] = None + message_key: Optional[str] = Field(None, title='Message Key') + + +class APIResponseUsageEventSummaryResponse(BaseModel): + status: str = Field(..., title='Status') + message: str = Field(..., title='Message') + status_code: Optional[int] = Field(0, title='Status Code') + data: Optional[UsageEventSummaryResponse] = None + message_key: Optional[str] = Field(None, title='Message Key') + + +class CreditsLedgerSummary(BaseModel): + start_date: Optional[datetime] = Field(None, title='Start Date') + end_date: Optional[datetime] = Field(None, title='End Date') + bucket: str = Field(..., title='Bucket') + total_entries: int = Field(..., title='Total Entries') + consume_count: int = Field(..., title='Consume Count') + grant_count: int = Field(..., title='Grant Count') + consumed_credits: float = Field(..., title='Consumed Credits') + granted_credits: float = Field(..., title='Granted Credits') + net_amount_credits: float = Field(..., title='Net Amount Credits') + max_amount_items: List[CreditsLedgerItem] = Field(..., title='Max Amount Items') + buckets: List[CreditsLedgerSummaryBucket] = Field(..., title='Buckets') + + +class HTTPValidationError(BaseModel): + detail: Optional[List[ValidationError]] = Field(None, title='Detail') + + +class UsageEventsSummary(BaseModel): + start_date: Optional[datetime] = Field(None, title='Start Date') + end_date: Optional[datetime] = Field(None, title='End Date') + bucket: str = Field(..., title='Bucket') + total_count: int = Field(..., title='Total Count') + success_count: int = Field(..., title='Success Count') + failure_count: int = Field(..., title='Failure Count') + charge_outcome_counts: Dict[str, int] = Field(..., title='Charge Outcome Counts') + pre_settlement_credits: float = Field(..., title='Pre Settlement Credits') + settled_credits: float = Field(..., title='Settled Credits') + max_charge_items: List[UsageEventItem] = Field(..., title='Max Charge Items') + buckets: List[UsageEventsSummaryBucket] = Field(..., title='Buckets') + + +class CreditsLedgerResponse(BaseModel): + items: List[CreditsLedgerItem] = Field(..., title='Items') + total: int = Field(..., title='Total') + page: int = Field(..., title='Page') + page_size: int = Field(..., title='Page Size') + summary: Optional[CreditsLedgerSummary] = None + + +class UsageEventsResponse(BaseModel): + items: List[UsageEventItem] = Field(..., title='Items') + total: int = Field(..., title='Total') + page: int = Field(..., title='Page') + page_size: int = Field(..., title='Page Size') + summary: Optional[UsageEventsSummary] = None + + +class APIResponseCreditsLedgerResponse(BaseModel): + status: str = Field(..., title='Status') + message: str = Field(..., title='Message') + status_code: Optional[int] = Field(0, title='Status Code') + data: Optional[CreditsLedgerResponse] = None + message_key: Optional[str] = Field(None, title='Message Key') + + +class APIResponseUsageEventsResponse(BaseModel): + status: str = Field(..., title='Status') + message: str = Field(..., title='Message') + status_code: Optional[int] = Field(0, title='Status Code') + data: Optional[UsageEventsResponse] = None + message_key: Optional[str] = Field(None, title='Message Key') diff --git a/packages/python-sdk/tests/test_openapi_contract.py b/packages/python-sdk/tests/test_openapi_contract.py new file mode 100644 index 0000000..432fd43 --- /dev/null +++ b/packages/python-sdk/tests/test_openapi_contract.py @@ -0,0 +1,66 @@ +"""Issue #37 phase 3 (#47): first low-risk adoption of the generated models. + +The hand-written models in ``qveris.types`` remain the public SDK surface. +This test starts *consuming* the generated contract reference +(``qveris.generated.openapi_models``) as a drift guard: it fails if the +generated module stops importing or if a core contract model the SDK depends +on disappears from the spec. It does not assert field-by-field equivalence — +aligning stable fields with the generated models is intentionally gradual. +""" + +import importlib.util +from pathlib import Path + +import pytest +from pydantic import BaseModel + +# Load the generated module by file path so the test does NOT execute +# qveris/__init__.py (which imports the full client: httpx, openai, ...). +# The generated artifact only depends on pydantic + stdlib, keeping this +# guard a true contract check with a minimal dependency surface. +_gen_path = Path(__file__).resolve().parents[1] / "qveris" / "generated" / "openapi_models.py" +_spec = importlib.util.spec_from_file_location("qveris_generated_openapi_models", _gen_path) +gen = importlib.util.module_from_spec(_spec) +_spec.loader.exec_module(gen) + +# Core contract models the Python SDK / CLI deserialize. Kept focused so the +# guard tracks toolkit-relevant drift without being brittle to unrelated +# backend schema churn. +CORE_MODELS = [ + "PublicSearchRequest", + "PublicSearchResponse", + "PublicCapabilityResult", + "PublicToolsByIdsRequest", + "PublicExecuteToolRequest", + "PublicExecuteToolResponse", + "PublicCompactBillingStatement", + "CreditsLedgerResponse", + "UsageEventsResponse", +] + + +def test_generated_module_imports(): + assert gen is not None + + +@pytest.mark.parametrize("name", CORE_MODELS) +def test_core_contract_model_present_and_pydantic(name): + model = getattr(gen, name, None) + assert model is not None, ( + f"{name} missing from generated contract — the public OpenAPI spec or " + f"the pinned generator drifted; regenerate qveris/generated/openapi_models.py" + ) + assert isinstance(model, type) and issubclass(model, BaseModel), ( + f"{name} is not a pydantic BaseModel" + ) + + +def test_search_request_contract_shape(): + # Structural drift signal on the request the SDK/CLI sends. (We assert on + # model_fields rather than instantiating: the generated file uses + # `from __future__ import annotations` + constrained types, so validation + # requires model_rebuild(); the field map is the stable contract anyway.) + fields = gen.PublicSearchRequest.model_fields + assert "query" in fields and fields["query"].is_required() + assert "limit" in fields and not fields["limit"].is_required() + assert "session_id" in fields and not fields["session_id"].is_required()