From 6b39c03bbaf735c117e32bcd6712f3af4408e8e4 Mon Sep 17 00:00:00 2001 From: Gokdeniz Kaymak Date: Tue, 8 Sep 2026 19:16:47 +0200 Subject: [PATCH] fix: integration skill fixes --- skills/apify-integration-development/SKILL.md | 2 +- .../references/ai-framework-package.md | 8 ++++---- .../references/ai-harness-plugin.md | 16 ++++++++-------- .../references/sdk-integration.md | 11 ++++++----- .../references/workflow-automation.md | 4 ++-- 5 files changed, 21 insertions(+), 20 deletions(-) diff --git a/skills/apify-integration-development/SKILL.md b/skills/apify-integration-development/SKILL.md index 268f83e..e14fc81 100644 --- a/skills/apify-integration-development/SKILL.md +++ b/skills/apify-integration-development/SKILL.md @@ -73,7 +73,7 @@ Polling must be **bounded**: use the run's own `timeoutSecs` plus a grace buffer Every path that starts a run must expose a cost control. The canonical control is `maxTotalChargeUsd` (caps the run's total charge on most pricing models) and `maxItems` (caps billed items on pay-per-result Actors). Send them as **options / query parameters**, never as Actor input - inside input they are either an Actor-declared field or simply invalid. `0` / empty / null means *no limit*. For LLM-facing integrations, the ceilings are **developer-controlled**; an LLM cannot widen them. ### Attribution headers -Stamp an integration header on every outbound request so Apify can attribute traffic: `x-apify-integration-platform: `. When a request is driven by an AI tool (not a human in a UI), also send `x-apify-integration-ai-tool: true`. One line, big telemetry payoff. +Stamp an integration header on every outbound request so Apify can attribute traffic: `x-apify-integration-platform: `. When a request is driven by an AI tool (not a human in a UI), also send `x-apify-integration-ai-tool: true`. If the integration was built using this skill, add `x-apify-integration-origin: apify-integration-development-skill` so Apify can distinguish skill-generated integrations from custom ones. One line, big telemetry payoff. ### Authentication - Browser / consumer-facing (a human completes a sign-in): OAuth2 with PKCE. Do not ask for raw tokens. diff --git a/skills/apify-integration-development/references/ai-framework-package.md b/skills/apify-integration-development/references/ai-framework-package.md index 8e1324a..786d777 100644 --- a/skills/apify-integration-development/references/ai-framework-package.md +++ b/skills/apify-integration-development/references/ai-framework-package.md @@ -67,14 +67,14 @@ A single predictable envelope lets agents parse results with one code path. The An LLM invoking a tool can request absurd values: 10,000 results, 32 GB of memory, a 1-hour timeout. Clamp every request to **developer-controlled ceilings**: -| Clamp | Default ceiling | +| Clamp | Default ceiling | Developer max | |---|---| | `timeout_secs` | 600 s | -| `memory_mbytes` | 32,768 MB (snapped to nearest valid power-of-2) | +| `memory_mbytes` | 4,096 MB (snapped to nearest valid power-of-2) | 8,192 MB | | `items` / `limit` | 1,000 | | `max_crawl_depth` | 5 | -Memory is notable: Apify accepts memory only as a power-of-2 (128, 256, 512, ... 32768). Snap an arbitrary LLM value to the nearest valid step at or below the developer's cap. +Memory is notable: Apify accepts memory only as a power-of-2 (128, 256, 512, ..., 32768). Snap an arbitrary LLM value to the nearest valid step at or below the developer's cap. The default ceiling of 4,096 MB (4 GB) is generous for most Actors but well below the platform max, so LLM-requested extremes are clamped. The developer can raise the ceiling up to 8,192 MB, but an LLM cannot widen it beyond the developer-set value. Some Actors have runtime limits not declared in their input schema (e.g. a RAG web browser rejects `maxResults > 100` at runtime). These can't be derived by schema introspection - track them by hand as overrides on the specific tool so the clamp enforces the Actor's real ceiling. @@ -155,4 +155,4 @@ The positioning: the package is the **programmatic, typed, registry-installable* - [ ] sdist allowlist excludes local paths; release automation drives versioning. - [ ] Unit tests are socket-disabled; lint/typing are strict. - [ ] README cross-references the MCP server for interactive/dynamic use. -- [ ] Attribution header / user-agent suffix is set on the client. \ No newline at end of file +- [ ] Attribution header / user-agent suffix is set on the client; skill-origin header included if built from this skill. \ No newline at end of file diff --git a/skills/apify-integration-development/references/ai-harness-plugin.md b/skills/apify-integration-development/references/ai-harness-plugin.md index 8c8aae0..5b218a1 100644 --- a/skills/apify-integration-development/references/ai-harness-plugin.md +++ b/skills/apify-integration-development/references/ai-harness-plugin.md @@ -90,7 +90,7 @@ Three tools cover the entire workflow and map cleanly to the asynchronous REST f | Tool | Purpose | Why | |---|---|---| | **discover** | Search Apify Store by keyword, OR fetch a single Actor's input schema + README by `actorId` | Two modes in one tool: an LLM that just got a list of Actor IDs almost always wants to inspect one next; splitting would double round-trips | -| **start** | Fire-and-forget batch starts (cap batch size, e.g. 10 per call) | Returns run references (`run_id`, `actor_id`, `default_dataset_id`, optional label) immediately without waiting | +| **start** | Fire-and-forget batch starts (cap batch size, e.g. 10 per call). Accepts cost limiting params (`maxTotalChargeUsd`, `maxItems`) sent as run options, never Actor input | Returns run references (`run_id`, `actor_id`, `default_dataset_id`, optional label) immediately without waiting | | **collect** | Poll run statuses and return completed dataset results | Re-call with the same run refs until `all_done` is true; return pending / completed / errored runs in separate arrays so the LLM keeps iterating on the pending ones | `collect` is the only one that needs to be async - it polls runs concurrently (`asyncio.gather` / `Promise.allSettled`) and pushes blocking SDK calls off the event loop. The other two are fast and single-shot. @@ -150,7 +150,7 @@ If the harness's `register()` is synchronous and the loader does not `await` it ## 8. SDK handling and attribution -Use the official `apify-client` SDK (JS or Python), not raw HTTP. Construct the client once, memoized, and rebuilt only when the token changes. Stamp the attribution header on every request: `x-apify-integration-platform: `. When the request is driven by the AI tool (always, in this shape), also send `x-apify-integration-ai-tool: true`. This is the single most important line for Apify's side of the relationship. +Use the official `apify-client` SDK (JS or Python), not raw HTTP. Construct the client once, memoized, and rebuilt only when the token changes. Stamp the attribution headers on every request: `x-apify-integration-platform: ` and `x-apify-integration-ai-tool: true`. If the integration was built using the Apify integration development skill, also set `x-apify-integration-origin: apify-integration-development-skill`. This is the single most important line for Apify's side of the relationship. **Compatibility shim:** SDK versions return a mix of Pydantic models and plain dicts, and Pydantic models expose only **snake_case** attributes even when the JSON is **camelCase**. Route *all* response reads through a small `_attr(obj, key, default)` helper that handles either shape. Direct `.attr` / `["key"]` access will silently return defaults on a mismatch. @@ -170,10 +170,9 @@ The tool factory should accept an optional injected `client`. When omitted, cons ## 12. Known gaps to design for -1. **Bill caps in the prompt, not the tool.** If `start` forwards only the Actor's `input` with no options channel, there is no `maxTotalChargeUsd` / `maxItems` cap plumbed through. The cost rule is enforced in the prompt via the README/pricing info `discover` returns. This is a known gap vs the MCP and REST paths - worth closing if the harness exposes an options argument. -2. **Poll vs webhook.** `collect` is an LLM-driven poll loop; long-running Actors mean multiple round-trips. A webhook-backed `collect` would be cheaper but requires the harness to expose a callback surface. -3. **Account-free discovery.** If the harness's `check_fn` gates all tools on a token, `discover` requires an account even for research. Consider giving `discover` a separate, looser check so users can browse before connecting. -4. **Surface scope.** Only the basic run-start -> poll -> fetch-dataset flow is exposed. Standby runs, Tasks, and schedules may be out of scope for v0.1 - document the boundary. +1. **Poll vs webhook.** `collect` is an LLM-driven poll loop; long-running Actors mean multiple round-trips. A webhook-backed `collect` would be cheaper but requires the harness to expose a callback surface. +2. **Account-free discovery.** If the harness's `check_fn` gates all tools on a token, `discover` requires an account even for research. Consider giving `discover` a separate, looser check so users can browse before connecting. +3. **Surface scope.** Only the basic run-start -> poll -> fetch-dataset flow is exposed. Standby runs, Tasks, and schedules may be out of scope for v0.1 - document the boundary. ## Definition-of-done checklist (Approach B) @@ -183,10 +182,11 @@ The tool factory should accept an optional injected `client`. When omitted, cons - [ ] Dataset output is untrusted-content fenced, size-capped, and marker-sanitized. - [ ] Errors are returned as data, never raised; partial batch failures are per-item. - [ ] Setup command verifies the token, reuses host config-merge, and has a manual fallback. -- [ ] Attribution headers (`-platform` and `-ai-tool`) are set on the client. +- [ ] Attribution headers (`-platform`, `-ai-tool`, and `-origin`) are set on the client. - [ ] All SDK response reads go through a compatibility shim. - [ ] Entry-point loader semantics verified; `register()` is synchronous if the loader does not await. - [ ] Schema uses string enums + `Optional`, no `anyOf`/`oneOf`; `input` is a record. - [ ] Actor IDs use the tilde form in all user/agent-facing surfaces. - [ ] Tool factory accepts an injected client; tests run with no network. -- [ ] Known gaps (bill caps, webhook, account-free discovery) are documented, not hidden. \ No newline at end of file +- [ ] Known gaps (webhook, account-free discovery) are documented, not hidden. +- [ ] Cost cap (`maxTotalChargeUsd` / `maxItems`) is plumbed through as run options on `start`, never Actor input. \ No newline at end of file diff --git a/skills/apify-integration-development/references/sdk-integration.md b/skills/apify-integration-development/references/sdk-integration.md index 61319f1..92d5eb1 100644 --- a/skills/apify-integration-development/references/sdk-integration.md +++ b/skills/apify-integration-development/references/sdk-integration.md @@ -9,7 +9,7 @@ Design guide for integrating Apify into an existing application by calling Actor Always install `apify-client`. Never install `apify` for integration work. Keep the dependency footprint small to minimize version conflicts and keep install time short. -Stamp a custom `user-agent` suffix or the attribution header (`x-apify-integration-platform: `) on the client so Apify can attribute traffic. +Stamp a custom `user-agent` suffix or the attribution header (`x-apify-integration-platform: `) on the client so Apify can attribute traffic. If the integration was built using the Apify integration development skill, also set `x-apify-integration-origin: apify-integration-development-skill`. ## 2. Token handling @@ -65,7 +65,8 @@ const run = await client.actor('apify/web-scraper').start({ }); // Poll for completion -const finishedRun = await client.run(run.id).waitForFinish(); +// Derive waitSecs from the run's timeoutSecs + a grace buffer, never unbounded. +const finishedRun = await client.run(run.id).waitForFinish({ waitSecs: 120 }); // Retrieve results const { items } = await client.dataset(finishedRun.defaultDatasetId).listItems(); @@ -99,7 +100,7 @@ try { const { items } = await client.dataset(run.defaultDatasetId).listItems(); } catch (error) { - if (error.message?.includes('not found')) { + if (error.type === 'record-not-found') { // Actor ID is wrong or Actor was deleted } else if (error.statusCode === 401) { // Invalid or missing APIFY_TOKEN @@ -205,7 +206,7 @@ REST reference: `https://docs.apify.com/api/v2`. OpenAPI spec: `https://apify.co ## 7. Best practices -- **Set timeouts:** pass `timeoutSecs` in the Actor input or `waitSecs` on `.call()` to avoid indefinite waits. +- **Set timeouts:** pass `timeoutSecs` as a run option / query parameter on `.call()` or `.start()`, or use `waitSecs` on `.call()`. Never put `timeoutSecs` in Actor input — it is a run option and an Actor whose schema rejects unknown fields will fail on it. - **Paginate large datasets:** use `limit` and `offset` when retrieving dataset items. - **Reuse clients:** create one `ApifyClient` instance and reuse it across calls. - **Handle Actor-specific input:** every Actor has its own input schema. Use `fetch-actor-details` MCP tool or append `.md` to the Actor's Store URL to get the schema before constructing input. @@ -227,9 +228,9 @@ If the Apify MCP server is available, use `search-apify-docs` and `fetch-apify-d - [ ] Token stored in env var / secret manager; never hardcoded or logged. - [ ] Actor input built from the schema (MCP `fetch-actor-details` or `.md` URL), not guessed. - [ ] Sync `.call()` used for short runs; async `.start()` + `.waitForFinish()` for long ones. +- [ ] Attribution header / user-agent suffix set on the client; skill-origin header included if built from this skill. - [ ] Cost cap (`maxTotalChargeUsd` / `max_total_charge_usd`) passed in options, never input. - [ ] Run status checked before consuming dataset; failed runs raise, not return empty. - [ ] Dataset and KV store retrieval covered; pagination on large datasets. - [ ] Errors mapped to actionable app-level messages. -- [ ] Attribution header / user-agent suffix set on the client. - [ ] REST API fallback documented for languages without a client. \ No newline at end of file diff --git a/skills/apify-integration-development/references/workflow-automation.md b/skills/apify-integration-development/references/workflow-automation.md index b1da30e..3712d95 100644 --- a/skills/apify-integration-development/references/workflow-automation.md +++ b/skills/apify-integration-development/references/workflow-automation.md @@ -117,7 +117,7 @@ Map the host's file-handling primitives (dehydration / stashing / signed URLs) t ## 10. Run-finished trigger UX Use the host's hook type backed by Apify webhooks: -- **Subscribe**: create an Apify webhook scoped to an `actorId` or `actorTaskId`, with `eventTypes` from the user-selected terminal statuses. The webhook's `requestUrl` is the host's target URL. Make registration **idempotent** - derive a key from `resource:id:sortedEvents` so re-activating a workflow does not create duplicate webhooks. Persist the created webhook ID so deactivation can delete it. +- **Subscribe**: create an Apify webhook scoped to an `actorId` or `actorTaskId`, with `eventTypes` from the user-selected terminal statuses. The webhook's `requestUrl` is the host's target URL. Make registration **idempotent** - derive a key from every field that distinguishes one registration from another (`resource:id:sortedEvents:requestUrl`) so re-activating a workflow does not create duplicate webhooks. Persist the created webhook ID so deactivation can delete it. - **Unsubscribe**: delete the webhook by its stored ID. - **Perform**: read the webhook payload and enrich it (section 7). - **Fallback list**: fetch the 3 most recent matching runs so users see realistic test data when configuring the trigger without waiting for a real event. @@ -159,5 +159,5 @@ Beyond generic "run Actor", ship a curated **Scrape single URL** action: a 2-fie - [ ] Error mapping is centralized; approval URLs are validated; codes don't clobber messages. - [ ] OAuth2 PKCE is the default consumer auth path; token fallback has a verify call. - [ ] A "Scrape single URL"-style convenience action exists with pre-run URL validation. -- [ ] `x-apify-integration-platform` header is sent on every outbound request. +- [ ] `x-apify-integration-platform` header is sent on every outbound request; `x-apify-integration-origin: apify-integration-development-skill` included if built from this skill. - [ ] Two test modes (mocked + live E2E) pass. \ No newline at end of file