Skip to content

feat: Add create-actor and update-actor-version tools - #1453

Draft
DaveHanns wants to merge 22 commits into
feat/get-actor-versionfrom
feat/update-actor-version
Draft

DaveHanns wants to merge 22 commits into
feat/get-actor-versionfrom
feat/update-actor-version

Conversation

@DaveHanns

@DaveHanns DaveHanns commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

What

Two write tools in the source category:

  • create-actor creates a private Actor with its first version and files. It never touches an existing Actor.
  • update-actor-version writes, edits, or deletes files of a version in one call. Files the call does not mention stay as they are.

Why

They replace the drafted push-actor (#1333), following the Actor source tools spec (#1452). Closes #1463. An edit sends only the changed text, not whole files.

How

  • create-actor: name, title, description, files, versionNumber (default 0.0), buildTag, and autoBuild. One POST creates the Actor and its version. It returns the files as the platform stored them, since the platform sets the name in .actor/actor.json.
  • update-actor-version: actor, versionNumber, operations, expectedRevision, and autoBuild.
    • write creates a file, or replaces one given its expectedHash.
    • edit replaces text; each oldText must match exactly once. In a file with CRLF line breaks, an LF oldText is retried as CRLF.
    • delete removes a file given its expectedHash. A rename is a delete and a write.
  • The operations apply to a fresh read of the version, all or nothing. A failed check names the operation, the file, and a reason code: FILE_EXISTS, FILE_NOT_FOUND, HASH_MISMATCH, NO_MATCH, MULTIPLE_MATCHES, NOT_TEXT, or REVISION_MISMATCH.
  • One PUT per call carries only sourceType and sourceFiles, never env vars or the build tag. A call that changes nothing sends no PUT.
  • Both tools: encoding defaults to base64 for binary extensions such as .png. warnings names empty files, which the build skips. autoBuild (default false) starts a build without waiting for it.
  • Left to the platform: permissions, Actor names, the 3 MiB limit, path length, and version numbers. Its error reaches the agent unchanged. The generic API tools (Add generic Apify API tools: search, operation details, read, and write #1443) cover env vars, build tags, Git sources, and replacing every file at once.
  • The version lookup, the refusal of versions not stored as files, and the URL credential hiding move from get_actor_version.ts to source_helpers.ts, so all source tools share them.

Testing

Unit tests cover each operation and reason code, all or nothing, the PUT body, CRLF edits, encodings, warnings, expectedRevision, refusals, API errors, cancellation, autoBuild, and schema conformance. Round-trip tests check that the revision these tools return is the one get-actor-version then reads. type-check, lint, format, test:unit, and check:agents pass. Not yet tried against the live API.

Notes

AI disclosure: implemented with Claude Code; awaiting human review.

🤖 Generated with Claude Code

Add two write tools to the opt-in source category. Both write only to the
caller's own account and store files inline in the version.

create-actor creates a private Actor with one version in a single POST, from
files (which must include .actor/actor.json) or a Git repository. It refuses a
name the account already uses, and warns about a missing Dockerfile and empty
files.

update-actor-version applies write, edit, delete, and move operations to a
fresh read of the version and saves the result with one version PUT that
carries only the source keys and buildTag, never envVars. Each operation has a
precondition (an absent path, expectedHash, or an exact-once match), and
expectedRevision guards replaceFiles and gitRepoUrl. A failure writes nothing
and names the operation, the path, and a reason code. It refuses zip-stored
versions and results over the platform's 3 MiB limit, and refuses file
operations on Git and gist versions. A save that lands between the read and
the PUT is not detected until the platform gets a conditional version PUT.

Both take autoBuild, which starts a build after the write and returns without
waiting. get-actor-version's description now says its hashes and revision are
what update-actor-version takes, when that tool is loaded.
- Check for a cancel right before the version PUT and the Actor POST, so a
  request cancelled during the reads writes nothing.
- create-actor builds its files, hashes, and revision from the files the
  platform stored, since it sets the name field of .actor/actor.json on
  create, and warns when that changed the file. A taken name (including the
  race case, name-not-unique) now gives the full name update-actor-version
  takes. The missing Dockerfile warning says the build uses the platform's
  default Node.js Dockerfile, and matches paths regardless of case.
- update-actor-version refuses a missing .actor/actor.json only when the
  changes remove it, or when a Git version switches to stored files.
- Sending back the Git URL that get-actor-version shows no longer removes
  the stored credentials. Another URL on the same host keeps them, with a
  warning, and a URL on another host is refused.
- Stored entries the call leaves alone go back verbatim and in place,
  duplicates included; a changed file keeps its stored name.
- Refuse a result where a file path is also a folder of other files or a
  folder entry.
- Cap the NO_MATCH context at 4 KiB, show a window of a long line, show the
  lines around newText when only it is found, and suggest reading again when
  no context is shown.
- Share splitLines, formatMib, the MiB and 255-character constants, and the
  500-file cap across the source tools, and give the 2 MiB refusal text that
  fits each tool.
- Output schema texts no longer name another tool, and describe sourceType
  and changes as the tool returns them.
- Add tests for the new rules and for the cases the reviewers found untested.
Move the files input rules, the manifest builder, and the missing Dockerfile
warning from create-actor to source_helpers.ts, so create-actor-version can
use the same rules. create-actor behaves as before.

(cherry picked from commit ea8fbb4)
The Apify API cannot change single files of an Actor's source, so a one-line edit reads the whole version and writes all its files back, which also leaves the gap between the read and the PUT. A TODO marks the place for when an API for atomic source changes exists.
# Conflicts:
#	src/tools/AGENTS.md
#	src/tools/source/get_actor_version.ts
#	src/tools/source/source_archive.ts
#	src/tools/source/source_files.ts
After the merge of get-actor-version's simplification:
- create-actor and update-actor-version use buildFilesManifest and
  isFolderEntry from source_files.ts, and source_helpers.ts drops its copies.
- The absolute path check (ABSOLUTE_NAME_REGEX) now lives in source_files.ts,
  since the zip reader it came from is gone.
- update-actor-version refuses a zip-stored version with the same wording as
  get-actor-version and no longer points to apify push.
# Conflicts:
#	src/tools/source/source_files.ts
Move the rule that turns a stored entry into bytes into source_files.ts
as decodeSourceFileEntry, and use it for the hash that get-actor-version
reports, for the unchanged-file check in update-actor-version, and for
the config comparison in create-actor. The three can no longer disagree
about how a missing format or content is read. A null content now reads
as empty in all three, as it already did for the hash.
# Conflicts:
#	README.md
#	src/tools/AGENTS.md
#	src/tools/source/get_actor_version.ts
#	src/tools/source/source_files.ts
…rsion simplification

The simplified get-actor-version dropped helpers that create-actor and update-actor-version still import. Define them in source_helpers.ts until the write tools are simplified too.
Narrow both tools to the updated spec and leave to the platform the checks it already makes.

- update-actor-version takes write, edit, and delete operations, reads the version from the one Actor GET, and saves it with one PUT of sourceType and sourceFiles, skipped when the revision does not change. Move, replaceFiles, gitRepoUrl, buildTag, allOccurrences, edit excerpts, match context, caps, and the size and path checks are gone. The edit logic moves in from source_edits.ts, which is deleted.
- create-actor takes files only and sends versionNumber and buildTag only when given. The name rules, token check, defaults, caps, path and base64 checks, actor.json rules, Git source, and extra warnings are gone.
- The Actor lookup no longer compares the owner with users/me, and API errors reach the agent unchanged.
- The Actor lookup, version choice, and refusal of versions not stored as files move to source_helpers.ts, shared with get-actor-version. The build start after a write and its next step are written once.
- Results are shorter: update returns revision, changed, and changes with each file's new hash; both return build or buildError and the empty-files warning.
… simplification

create-actor sends version number 0.0 again when the caller gives none. The platform refuses a version without a number, so the default call failed and created nothing. The build tag is still sent only when given, since the platform sets latest on the only version.

The create-actor test mock no longer fills in a version number the platform does not add, and the update-actor-version test comment now describes the PUT file order the code produces.
# Conflicts:
#	tests/unit/tools.mode_contract.test.ts
Any other error from starting the build is a bug, so it goes to the tool-call engine
as a server error instead of reading as a failed start.
# Conflicts:
#	README.md
#	src/tools/registry.ts
…ks, and unnumbered versions in update-actor-version

Assert that empty files and files stored without content are saved, both
the ones the call leaves alone and the ones it writes empty. Add an edit
with two replacements on different lines where the first changes the
length. Refuse an empty or 8-character expectedHash and expectedRevision.
Refuse to pick a default version when the Actor has no numbered one. Add
an http:// row to the URL credential table. Assert the whole PUT in the
rename, path-matching, binary-default, and in-order edit tests.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

2 participants